{ "cells": [ { "cell_type": "markdown", "id": "3daa466f-2ac1-42cc-a2a8-49aa2868756a", "metadata": {}, "source": [ "# Mendeleev's Periodic Table of Elements\n", "\n", "The notebook is inspired by [this](https://plotnine.org/reference/geom_tile#periodic-table-of-elements) and [this](https://docs.bokeh.org/en/latest/docs/examples/topics/categorical/periodic.html) examples.\n", "\n", "The data is available under the Creative Commons Attribution-ShareAlike 4.0 International Public License (CC BY-SA 4.0). For more details, see [here](https://github.com/dataexplorer/datasets/blob/master/License.md) or visit [Data Explorer](https://www.data-explorer.com/data/)." ] }, { "cell_type": "code", "execution_count": 1, "id": "3d348f34-25f1-4894-810a-2fa817728038", "metadata": { "execution": { "iopub.execute_input": "2024-04-26T12:19:54.302624Z", "iopub.status.busy": "2024-04-26T12:19:54.302624Z", "iopub.status.idle": "2024-04-26T12:19:55.259961Z", "shell.execute_reply": "2024-04-26T12:19:55.259961Z" } }, "outputs": [], "source": [ "import pandas as pd\n", "\n", "from lets_plot import *" ] }, { "cell_type": "code", "execution_count": 2, "id": "55f8dd04-e953-47d6-8fc0-623ad8ad5add", "metadata": { "execution": { "iopub.execute_input": "2024-04-26T12:19:55.259961Z", "iopub.status.busy": "2024-04-26T12:19:55.259961Z", "iopub.status.idle": "2024-04-26T12:19:55.275589Z", "shell.execute_reply": "2024-04-26T12:19:55.275589Z" } }, "outputs": [ { "data": { "text/html": [ "\n", "
\n", " \n", " " ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "LetsPlot.setup_html()" ] }, { "cell_type": "code", "execution_count": 3, "id": "9f83efe0-23ff-4c98-bb37-9d69e6afda26", "metadata": { "execution": { "iopub.execute_input": "2024-04-26T12:19:55.275589Z", "iopub.status.busy": "2024-04-26T12:19:55.275589Z", "iopub.status.idle": "2024-04-26T12:19:55.573355Z", "shell.execute_reply": "2024-04-26T12:19:55.573355Z" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "(118, 23)\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
Atomic NumberElementSymbolAtomic WeightPeriodGroupPhaseMost Stable CrystalTypeIonic Radius...DensityMelting Point (K)Boiling Point (K)IsotopesDiscovererYear of DiscoverySpecific Heat CapacityElectron ConfigurationDisplay RowDisplay Column
01HydrogenH1.00794011gasNaNNonmetal0.012...0.00009014.17520.283.0Cavendish1766.014.3041s111
12HeliumHe4.002602118gasNaNNoble GasNaN...0.000179NaN4.225.0Janssen1868.05.1931s2118
23LithiumLi6.94100021solidbccAlkali Metal0.760...0.534000453.8501615.005.0Arfvedson1817.03.582[He] 2s121
34BerylliumBe9.01218222solidhexAlkaline Earth Metal0.350...1.8500001560.1502742.006.0Vaulquelin1798.01.825[He] 2s222
45BoronB10.811000213solidrhoMetalloid0.230...2.3400002573.1504200.006.0Gay-Lussac1808.01.026[He] 2s2 2p1213
\n", "

5 rows × 23 columns

\n", "
" ], "text/plain": [ " Atomic Number Element Symbol Atomic Weight Period Group Phase \\\n", "0 1 Hydrogen H 1.007940 1 1 gas \n", "1 2 Helium He 4.002602 1 18 gas \n", "2 3 Lithium Li 6.941000 2 1 solid \n", "3 4 Beryllium Be 9.012182 2 2 solid \n", "4 5 Boron B 10.811000 2 13 solid \n", "\n", " Most Stable Crystal Type Ionic Radius ... Density \\\n", "0 NaN Nonmetal 0.012 ... 0.000090 \n", "1 NaN Noble Gas NaN ... 0.000179 \n", "2 bcc Alkali Metal 0.760 ... 0.534000 \n", "3 hex Alkaline Earth Metal 0.350 ... 1.850000 \n", "4 rho Metalloid 0.230 ... 2.340000 \n", "\n", " Melting Point (K) Boiling Point (K) Isotopes Discoverer \\\n", "0 14.175 20.28 3.0 Cavendish \n", "1 NaN 4.22 5.0 Janssen \n", "2 453.850 1615.00 5.0 Arfvedson \n", "3 1560.150 2742.00 6.0 Vaulquelin \n", "4 2573.150 4200.00 6.0 Gay-Lussac \n", "\n", " Year of Discovery Specific Heat Capacity Electron Configuration \\\n", "0 1766.0 14.304 1s1 \n", "1 1868.0 5.193 1s2 \n", "2 1817.0 3.582 [He] 2s1 \n", "3 1798.0 1.825 [He] 2s2 \n", "4 1808.0 1.026 [He] 2s2 2p1 \n", "\n", " Display Row Display Column \n", "0 1 1 \n", "1 1 18 \n", "2 2 1 \n", "3 2 2 \n", "4 2 13 \n", "\n", "[5 rows x 23 columns]" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def get_elements_df():\n", " df = pd.read_csv(\"https://raw.githubusercontent.com/JetBrains/lets-plot-docs/master/data/chemical_elements.csv\", encoding_errors='ignore')\n", " # Fixes and updates in data\n", " df.loc[df[\"Element\"] == \"Francium\", \"Type\"] = \"Alkali Metal\"\n", " df.loc[df[\"Element\"] == \"Radium\", \"Type\"] = \"Alkaline Earth Metal\"\n", " df.loc[df[\"Element\"] == \"Astatine\", \"Type\"] = \"Halogen\"\n", " df.loc[df[\"Element\"] == \"Radon\", \"Type\"] = \"Noble Gas\"\n", " df.loc[df[\"Atomic Number\"] == 113, \"Element\"] = \"Nihonium\"\n", " df.loc[df[\"Atomic Number\"] == 113, \"Symbol\"] = \"Nh\"\n", " df.loc[df[\"Atomic Number\"] == 113, \"Type\"] = \"Metal\"\n", " df.loc[df[\"Atomic Number\"] == 114, \"Element\"] = \"Flerovium\"\n", " df.loc[df[\"Atomic Number\"] == 114, \"Symbol\"] = \"Fl\"\n", " df.loc[df[\"Atomic Number\"] == 114, \"Type\"] = \"Metal\"\n", " df.loc[df[\"Atomic Number\"] == 115, \"Element\"] = \"Moscovium\"\n", " df.loc[df[\"Atomic Number\"] == 115, \"Symbol\"] = \"Mc\"\n", " df.loc[df[\"Atomic Number\"] == 115, \"Type\"] = \"Metal\"\n", " df.loc[df[\"Atomic Number\"] == 116, \"Element\"] = \"Livermorium\"\n", " df.loc[df[\"Atomic Number\"] == 116, \"Symbol\"] = \"Lv\"\n", " df.loc[df[\"Atomic Number\"] == 116, \"Type\"] = \"Metal\"\n", " df.loc[df[\"Atomic Number\"] == 117, \"Element\"] = \"Tennessine\"\n", " df.loc[df[\"Atomic Number\"] == 117, \"Symbol\"] = \"Ts\"\n", " df.loc[df[\"Atomic Number\"] == 117, \"Type\"] = \"Halogen\"\n", " df.loc[df[\"Atomic Number\"] == 118, \"Element\"] = \"Oganesson\"\n", " df.loc[df[\"Atomic Number\"] == 118, \"Symbol\"] = \"Og\"\n", " df.loc[df[\"Atomic Number\"] == 118, \"Type\"] = \"Noble Gas\"\n", " df.loc[df[\"Type\"] == \"Transactinide\", \"Type\"] = \"Transition Metal\"\n", " return df\n", "\n", "def prepare_top_df(filtered_df):\n", " return filtered_df.assign(\n", " X=lambda df: df[\"Group\"],\n", " Y=lambda df: df[\"Period\"],\n", " )\n", "\n", "def prepare_bottom_df(filtered_df):\n", " import numpy as np\n", "\n", " nrows = 2\n", " hshift = 3\n", " vshift = 2.5\n", " return filtered_df.assign(\n", " X=np.tile(np.arange(len(filtered_df) // nrows), nrows) + hshift,\n", " Y=filtered_df[\"Period\"] + vshift\n", " )\n", "\n", "def get_extra_top_df():\n", " return pd.DataFrame({\n", " \"X\": [3, 3],\n", " \"Y\": [6, 7],\n", " \"Type\": [\"Lanthanide\", \"Actinide\"],\n", " \"Range\": [\"57-71\", \"89-103\"],\n", " })\n", "\n", "def get_table_key_df(df, x, y, *, atomic_number):\n", " return df[df[\"Atomic Number\"] == atomic_number].assign(X=[x], Y=[y])\n", "\n", "def get_group_df(df):\n", " result = df.groupby(\n", " \"Group\"\n", " ).agg(\n", " Y=(\"Period\", 'min')\n", " ).reset_index()\n", " result[\"Group\"] = result[\"Group\"].astype(int)\n", " return result\n", "\n", "def get_period_df(min_value, max_value):\n", " return {\n", " \"X\": [0] * (max_value - min_value + 1),\n", " \"Period\": list(range(min_value, max_value + 1)),\n", " }\n", "\n", "def get_annotations_df(x, y):\n", " return {\n", " \"X\": [x+.8, x+1, x+.4],\n", " \"Y\": [y-.9, y+.1, y+1],\n", " \"Label\": [\"Atomic Number\", \"Symbol\", \"Atomic Mass\"],\n", " }\n", "\n", "elements_df = get_elements_df()\n", "print(elements_df.shape)\n", "elements_df.head()" ] }, { "cell_type": "code", "execution_count": 4, "id": "b93b9351-d0e4-4f80-a4c0-c39f84cf8524", "metadata": { "execution": { "iopub.execute_input": "2024-04-26T12:19:55.573355Z", "iopub.status.busy": "2024-04-26T12:19:55.573355Z", "iopub.status.idle": "2024-04-26T12:19:55.589016Z", "shell.execute_reply": "2024-04-26T12:19:55.589016Z" } }, "outputs": [], "source": [ "tile_side = .95\n", "tile_ratio = 1.2\n", "table_key_size_ratio = 1.5\n", "table_key_x, table_key_y = 10.25, 1.25" ] }, { "cell_type": "code", "execution_count": 5, "id": "2de3daea-b80e-415e-ace6-1fe4e7ea2d02", "metadata": { "execution": { "iopub.execute_input": "2024-04-26T12:19:55.589016Z", "iopub.status.busy": "2024-04-26T12:19:55.589016Z", "iopub.status.idle": "2024-04-26T12:19:55.605108Z", "shell.execute_reply": "2024-04-26T12:19:55.605108Z" } }, "outputs": [], "source": [ "bottom_filter = lambda df: (df[\"Type\"] == \"Actinide\")|(df[\"Type\"] == \"Lanthanide\")\n", "\n", "top_df = prepare_top_df(elements_df[~bottom_filter(elements_df)])\n", "bottom_df = prepare_bottom_df(elements_df[bottom_filter(elements_df)])\n", "extra_top_df = get_extra_top_df()\n", "table_key_df = get_table_key_df(elements_df, table_key_x, table_key_y, atomic_number=78)\n", "group_df = get_group_df(top_df)\n", "period_df = get_period_df(1, 7)\n", "annotations_df = get_annotations_df(table_key_x, table_key_y)" ] }, { "cell_type": "code", "execution_count": 6, "id": "00c7d374-eb54-44a6-a28b-34d63559dfd3", "metadata": { "execution": { "iopub.execute_input": "2024-04-26T12:19:55.605108Z", "iopub.status.busy": "2024-04-26T12:19:55.605108Z", "iopub.status.idle": "2024-04-26T12:19:55.746752Z", "shell.execute_reply": "2024-04-26T12:19:55.746752Z" } }, "outputs": [ { "data": { "text/html": [ "
\n", " " ], "text/plain": [ "" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "def inner_text(df, *, ratio=1):\n", " if 'Range' in df.columns:\n", " return geom_text(aes(label=\"Range\"), data=df, nudge_y=.05*ratio, size=5*ratio, fontface='bold') + \\\n", " geom_text(aes(label=\"Type\"), data=df, nudge_y=-.2*ratio, size=4*ratio)\n", " else:\n", " return geom_text(aes(label=\"Atomic Number\"), data=df, nudge_x=-.37*ratio, nudge_y=.37*ratio, hjust='left', vjust='top', size=5*ratio) + \\\n", " geom_text(aes(label=\"Symbol\"), data=df, nudge_y=.05*ratio, size=7*ratio, fontface='bold') + \\\n", " geom_text(aes(label=\"Atomic Weight\"), data=df, nudge_y=-.2*ratio, size=4*ratio, label_format=\".3~f\")\n", "\n", "def table_key_annotations(x, y):\n", " table_key_arrow = arrow(angle=30, length=4, type='closed')\n", " return geom_curve(x=x+.7, y=y-.9, xend=x-.3, yend=y-.6, curvature=.4, ncp=1, arrow=table_key_arrow) + \\\n", " geom_segment(x=x+.9, y=y+.1, xend=x+.3, yend=y-.1, curvature=.3, arrow=table_key_arrow) + \\\n", " geom_curve(x=x+.3, y=y+1, xend=x, yend=y+.5, curvature=-.4, ncp=1, arrow=table_key_arrow)\n", "\n", "element_tooltips = layer_tooltips().title(\"@Element\\n(@Type)\")\\\n", " .line(\"@|@{Atomic Number}\")\\\n", " .line(\"Atomic Mass|@{Atomic Weight}\")\\\n", " .line(\"@|@{Electron Configuration}\")\n", "table_theme = theme(plot_title=element_text(size=26, face='bold', margin=[30, 0, 5, 0], hjust=.5), \\\n", " plot_caption=element_text(size=18), \\\n", " plot_background=element_rect(color='black', size=3), \\\n", " legend_position=[.36, .85], \\\n", " legend_background='blank')\n", "\n", "ggplot(mapping=aes(\"X\", \"Y\", fill=\"Type\")) + \\\n", " geom_tile(data=top_df, color='black', size=.25, width=tile_side, height=tile_side, tooltips=element_tooltips) + \\\n", " geom_tile(data=extra_top_df, color='black', size=.25, width=tile_side, height=tile_side, tooltips='none') + \\\n", " geom_tile(data=bottom_df, color='black', size=.25, width=tile_side, height=tile_side, tooltips=element_tooltips) + \\\n", " geom_tile(data=table_key_df, color='black', size=.25, width=table_key_size_ratio*tile_side, height=table_key_size_ratio*tile_side, tooltips='none') + \\\n", " inner_text(top_df) + \\\n", " inner_text(extra_top_df) + \\\n", " inner_text(bottom_df) + \\\n", " inner_text(table_key_df, ratio=table_key_size_ratio) + \\\n", " geom_text(aes(\"Group\", \"Y\", label=\"Group\"), data=group_df, \\\n", " color='gray', nudge_y=.525, vjust='bottom', size=6) + \\\n", " geom_text(aes(\"X\", \"Period\", label=\"Period\"), data=period_df, \\\n", " color='gray', nudge_x=.375, vjust='right', size=6) + \\\n", " geom_text(aes(label=\"Label\"), data=annotations_df, hjust=0) + \\\n", " table_key_annotations(table_key_x, table_key_y) + \\\n", " scale_y_reverse() + \\\n", " scale_fill_brewer(name='', type='qual', palette='Set2', guide=guide_legend(ncol=2)) + \\\n", " coord_fixed(ratio=tile_ratio) + \\\n", " labs(title=\"Periodic Table of Chemical Elements\", caption=\"© 1869, Dmitri Mendeleev\") + \\\n", " ggsize(1000, 700) + \\\n", " theme_void() + table_theme" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.10.13" } }, "nbformat": 4, "nbformat_minor": 5 }