{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Extending Pandas DataFrame Class\n", "\n", "In this notebook we show how to extend the Pandas DataFrame class using the IsotopeAbundances class defined in tardis/io/decay.py. Official Pandas documentation is located here: https://pandas.pydata.org/pandas-docs/stable/development/extending.html" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "import astropy.units as u" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "# Notice that we add the new property 'time_0' to _metadata\n", "# and that we overwrite the __init__() function.\n", "\n", "class IsotopeAbundances(pd.DataFrame):\n", "\n", " _metadata = [\"time_0\"]\n", " \n", " def __init__(self, *args, **kwargs):\n", " if 'time_0' in kwargs:\n", " time_0 = kwargs['time_0']\n", " kwargs.pop('time_0')\n", " else:\n", " time_0 = 0 * u.d\n", " super(IsotopeAbundances, self).__init__(*args, **kwargs)\n", " self.time_0 = time_0\n", " \n", " @property\n", " def _constructor(self):\n", " return IsotopeAbundances\n", "\n", "\n", " def _update_material(self):\n", " self.comp_dicts = [dict() for i in range(len(self.columns))]\n", " for (atomic_number, mass_number), abundances in self.iterrows():\n", " nuclear_symbol = '%s%d'.format(nucname.name(atomic_number),\n", " mass_number)\n", " for i in range(len(self.columns)):\n", " self.comp_dicts[i][nuclear_symbol] = abundances[i]\n", "\n", " @classmethod\n", " def from_materials(cls, materials):\n", " multi_index_tuples = set([])\n", " for material in materials:\n", " multi_index_tuples.update([cls.id_to_tuple(key)\n", " for key in material.keys()])\n", "\n", " index = pd.MultiIndex.from_tuples(\n", " multi_index_tuples, names=['atomic_number', 'mass_number'])\n", "\n", "\n", " abundances = pd.DataFrame(data=0.0, index=index, columns=range(len(materials)))\n", "\n", " for i, material in enumerate(materials):\n", " for key, value in material.items():\n", " abundances.loc[cls.id_to_tuple(key), i] = value\n", "\n", " return cls(abundances)\n", "\n", "\n", "\n", "\n", " @staticmethod\n", " def id_to_tuple(atomic_id):\n", " return nucname.znum(atomic_id), nucname.anum(atomic_id)\n", "\n", "\n", " def to_materials(self):\n", " \"\"\"\n", " Convert DataFrame to a list of materials interpreting the MultiIndex as\n", " atomic_number and mass_number\n", "\n", " Returns\n", " -------\n", " : ~list\n", " list of pyne Materialss\n", " :return:\n", " \"\"\"\n", "\n", " comp_dicts = [dict() for i in range(len(self.columns))]\n", " for (atomic_number, mass_number), abundances in self.iterrows():\n", " nuclear_symbol = '{0:s}{1:d}'.format(nucname.name(atomic_number),\n", " mass_number)\n", " for i in range(len(self.columns)):\n", " comp_dicts[i][nuclear_symbol] = abundances[i]\n", " return [material.Material(comp_dict) for comp_dict in comp_dicts]\n", "\n", "\n", "\n", " def decay(self, t):\n", " \"\"\"\n", " Decay the Model\n", "\n", " Parameters\n", " ----------\n", "\n", " t: ~float or ~astropy.units.Quantity\n", " if float it will be understood as days\n", "\n", " Returns:\n", " : decayed abundances\n", " \"\"\"\n", "\n", " materials = self.to_materials()\n", " t_second = u.Quantity(t, u.day).to(u.s).value - self.time_0.to(u.s).value\n", " decayed_materials = [item.decay(t_second) for item in materials]\n", " for i in range(len(materials)):\n", " materials[i].update(decayed_materials[i])\n", " df = IsotopeAbundances.from_materials(materials)\n", " df.sort_index(inplace=True)\n", " return df \n", "\n", " def as_atoms(self):\n", " \"\"\"\n", " Merge Isotope dataframe according to atomic number \n", "\n", " Returns:\n", " : merged isotope abundances\n", " \"\"\"\n", "\n", " return self.groupby('atomic_number').sum()\n", "\n", " def merge(self, other, normalize=True):\n", " \"\"\"\n", " Merge Isotope dataframe with abundance passed as parameter \n", "\n", " Parameters\n", " ----------\n", " other: pd.DataFrame \n", " normalize : bool\n", " If true, resultant dataframe will be normalized\n", "\n", " Returns:\n", " : merged abundances\n", " \"\"\"\n", " isotope_abundance = self.as_atoms()\n", " isotope_abundance = isotope_abundance.fillna(0.0)\n", " #Merge abundance and isotope dataframe\n", " modified_df = isotope_abundance.add(other, fill_value=0)\n", "\n", " if normalize:\n", " norm_factor = modified_df.sum(axis=0)\n", " modified_df /= norm_factor\n", "\n", " return modified_df" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "df = IsotopeAbundances({'A': [1, 2, 3], 'B': [4, 5, 6], 'C': [7, 8, 9]}, time_0=5*u.d)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now the new property is accessible:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/latex": [ "$5 \\; \\mathrm{d}$" ], "text/plain": [ "" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "df.time_0" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.7.8" } }, "nbformat": 4, "nbformat_minor": 2 }