From c7b218516414740358e7264363d35ed2e8fa322e Mon Sep 17 00:00:00 2001 From: Saptak Das <66028584+Saptak625@users.noreply.github.com> Date: Sat, 11 Mar 2023 13:46:44 -0500 Subject: [PATCH] Added Aggregate and Table Operations --- docs/_static/example.xls | Bin 0 -> 36352 bytes docs/_static/example.xlsx | Bin 0 -> 10729 bytes docs/_static/output.xls | Bin 0 -> 44032 bytes docs/_static/output.xlsx | Bin 0 -> 13465 bytes docs/aggregate_operations.rst | 27 + docs/conf.py | 2 +- docs/index.rst | 2 + docs/lab_example.rst | 86 ++- docs/table_operations.rst | 58 ++ docs/test.ipynb | 1068 ++++++++++++++++++++++-------- pyproject.toml | 2 +- src/pymeasurement/measurement.py | 81 ++- 12 files changed, 1035 insertions(+), 291 deletions(-) create mode 100644 docs/_static/example.xls create mode 100644 docs/_static/example.xlsx create mode 100644 docs/_static/output.xls create mode 100644 docs/_static/output.xlsx create mode 100644 docs/aggregate_operations.rst create mode 100644 docs/table_operations.rst diff --git a/docs/_static/example.xls b/docs/_static/example.xls new file mode 100644 index 0000000000000000000000000000000000000000..b3d8aaf4a17ae3ffacaa6ee8a2a9c28d00732a5d GIT binary patch literal 36352 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 iNDLbf1Y^ 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 literal 0 HcmV?d00001 diff --git a/docs/_static/output.xlsx b/docs/_static/output.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..f9a8a360fc06c7792ba3f2d46914e3900ffdfb93 GIT binary patch literal 13465 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 literal 0 HcmV?d00001 diff --git a/docs/aggregate_operations.rst b/docs/aggregate_operations.rst new file mode 100644 index 0000000..cb5f6cb --- /dev/null +++ b/docs/aggregate_operations.rst @@ -0,0 +1,27 @@ +Aggregate Operations +======================= + +The following operations are available for iterable collections of measurements: + +* ``sum``: Sum a collection of measurements. +* ``min``: Find the minimum of a collection of measurements. +* ``max``: Find the maximum of a collection of measurements. +* ``average``: Find the average of a collection of measurements (Uncertainty = :math:`\Delta x = \frac{x_{max}-x_{min}}{2\sqrt{n}}`). + +All of these operations return a new measurement object. + +.. testsetup:: * + + from pymeasurement import Measurement as M + +.. doctest:: python + + >>> collection = [M.fromStr('20.23d g'), M.fromStr('13.86d g'), M.fromStr('46.37d g')] + >>> M.sum(collection) + 80.46 +/- 0.03 g + >>> M.min(collection) + 13.86 +/- 0.01 g + >>> M.max(collection) + 46.37 +/- 0.01 g + >>> M.average(collection) + 26.82 +/- 9.38 g diff --git a/docs/conf.py b/docs/conf.py index 4beb073..8c710e4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -13,7 +13,7 @@ project = 'pymeasurement' copyright = '2023, Saptak Das' author = 'Saptak Das' -release = '1.0.7' +release = '1.0.8' # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration diff --git a/docs/index.rst b/docs/index.rst index 234dd61..2796fad 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,8 @@ View on `PyPi `_ and on `Github >> + >>> from pymeasurement import Measurement as M + >>> decimals = [3, 3, 3, 1, 1] + >>> units = ['g', 'g', 'g', 'ºC', 'ºC'] + >>> for i in range(5): + ... df.iloc[:, i + 3] = M.importColumn(df.iloc[:, i + 3], d=True, un=units[i], decimals=decimals[i]) + +Now using the below formulas, the enthalpy of combustion can be calculated for each trial. + +.. math:: + + Q = m c \Delta T = m c (T_f - T_i) + +.. math:: + + n = m / M + +.. math:: + + \Delta H = -\frac{Q}{n} + +.. doctest:: python + + >>> heat_capacity = M.fromStr('4.18c J/g*ºC') + >>> j_to_kj = M.fromStr('0.001c kJ/J') + >>> results_df_1 = df.iloc[:, 0:3] + >>> results_df_1['Q of H2O (kJ)'] = df['Mass of water (+/- 0.001 g)'] * heat_capacity * (df['Final temperature (+/- 0.1 ºC)'] - df['Initial temperature (+/- 0.1 ºC)']) * j_to_kj + >>> results_df_1['Mass of Alcohol (g)'] = df['Initial mass of spirit burner (+/- 0.001 g)'] - df['Final mass of spirit burner (+/- 0.001 g)'] + >>> molar_masses = {'Ethanol': M.fromStr('46.08c g/mol'), 'Propan-1-ol': M.fromStr('60.11c g/mol'), 'Butan-1-ol': M.fromStr('74.14c g/mol'), 'Pentan-1-ol': M.fromStr('88.17c g/mol')} + >>> results_df_1['Molar Mass of Alcohol (g/mol)'] = results_df_1.apply(lambda x: x['Mass of Alcohol (g)'] / molar_masses[x['Alcohol tested']], axis=1) + >>> results_df_1['Molar Enthalpy of Combustion (kJ/mol)'] = - results_df_1['Q of H2O (kJ)'] / results_df_1['Molar Mass of Alcohol (g/mol)'] + +**Results Table 1: Individual Molar Enthalpy of Combustion** + +.. exceltable:: + :file: _static\output.xls + :header: 1 + :selection: A1:G24 + +Now, to calculate the average molar enthalpy of combustion for each of the alcohols, the data is grouped by alcohol type and the average is taken. + +.. doctest:: python + + >>> results_df_2 = pd.DataFrame() + >>> results_df_2['Alcohol'] = results_df_1['Alcohol tested'].unique() + >>> grouped_data = list(results_df_1.groupby('Alcohol tested')['Molar Enthalpy of Combustion (kJ/mol)']) + >>> grouped_data_dict = {i[0]: i[1] for i in grouped_data} + >>> results_df_2['Average Molar Enthalpy of Combustion (kJ/mol)'] = [M.average(list(grouped_data_dict[i])).percent() for i in results_df_2['Alcohol']] + >>> results_df_2['Accepted Molar Enthalpy of Combustion (kJ/mol)'] = [M.fromStr('-1367c kJ/mol'), M.fromStr('-2021c kJ/mol'), M.fromStr('-2676c kJ/mol'), M.fromStr('-3329c kJ/mol')] + >>> results_df_2['Percent Error (%)'] = results_df_2.apply(lambda x: ((x['Accepted Molar Enthalpy of Combustion (kJ/mol)'] - x['Average Molar Enthalpy of Combustion (kJ/mol)']) * 100 / x['Accepted Molar Enthalpy of Combustion (kJ/mol)']), axis=1) + +**Results Table 2: Average Molar Enthalpy of Combustion** + +.. exceltable:: + :file: _static\output.xls + :header: 1 + :selection: J1:M5 + + +Finally, we can convert the Measurement columns back into standard numeric columns. + +.. doctest:: python + + >>> final_results_df_1 = results_df_1.copy() + >>> for i in range(4): + ... M.exportColumn(final_results_df_1, results_df_1.iloc[:, i + 3]) + >>> final_results_df_2 = results_df_2.copy() + >>> final_results_df_2 = results_df_2.copy() + >>> for i in range(3): + ... M.exportColumn(final_results_df_2, results_df_2.iloc[:, i + 1], addUncertainty=i==0) + +**Final Results Table 1: Individual Molar Enthalpy of Combustion** + +.. exceltable:: + :file: _static\output.xls + :header: 1 + :selection: A31:K54 + +**Final Results Table 2: Average Molar Enthalpy of Combustion** + +.. exceltable:: + :file: _static\output.xls + :header: 1 + :selection: P31:T35 \ No newline at end of file diff --git a/docs/table_operations.rst b/docs/table_operations.rst new file mode 100644 index 0000000..52c876f --- /dev/null +++ b/docs/table_operations.rst @@ -0,0 +1,58 @@ +Table Operations +================ + +pymeasurement can be used with Pandas DataFrames to perform precision-based uncertainty calculations on tables of data. + +**Original Data Table** + +.. exceltable:: + :file: _static\example.xls + :header: 1 + :selection: A1:C7 + +In order to acheive this, the DataFrame is converted into Measurement objects for calculations using the below. + +All data can be normalized in this step as well. + +.. doctest:: python + + >>> import pandas as pd + >>> df = pd.read_excel('_static\example.xlsx') + >>> from pymeasurement import Measurement as M + >>> converted = pd.DataFrame() + >>> converted['Mass (± 0.001 kg)'] = M.importColumn(df['Mass (± 0.001 kg)'], d=True, un='kg', decimals=3) + >>> converted['Average Acceleration (m/s^2)'] = M.importColumn(df['Average Acceleration (m/s^2)'], uncertaintyColumn=df['Average Acceleration Percent Uncertainty (%)'], df=df, up=True, un='m/s^2', decimals=2) + +**Converted Data Table** + +.. exceltable:: + :file: _static\example.xls + :header: 1 + :selection: F1:G7 + +Now calculations can easily be performed on the DataFrame using the Measurement objects. + +.. doctest:: python + + >>> converted['Force (N)'] = converted['Mass (± 0.001 kg)'] * converted['Average Acceleration (m/s^2)'] + +**Calculated Data Table** + +.. exceltable:: + :file: _static\example.xls + :header: 1 + :selection: J1:L7 + +Once the calculations are complete, the DataFrame can be converted back into a numeric types using the below. + +.. doctest:: python + + >>> final_table = converted.copy() + >>> M.exportColumn(final_table, converted['Mass (± 0.001 kg)'], addUncertainty=False) + >>> M.exportColumn(final_table, converted['Average Acceleration (m/s^2)']) + >>> M.exportColumn(final_table, converted['Force (N)'], asPercent=False) + +.. exceltable:: + :file: _static\example.xls + :header: 1 + :selection: O1:S7 diff --git a/docs/test.ipynb b/docs/test.ipynb index 50bca3e..29b6102 100644 --- a/docs/test.ipynb +++ b/docs/test.ipynb @@ -2,24 +2,19 @@ "cells": [ { "cell_type": "code", - "execution_count": 105, + "execution_count": 41, "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", + "from pymeasurement import Measurement as M, SigFig\n", "\n", - "df = pd.read_excel('_static\\Combustion_Lab_1920_Student_Data.xlsx')\n", - "\n", - "# Go to src\n", - "import sys\n", - "import os\n", - "sys.path.insert(0, os.path.join(os.path.dirname('test.ipynb'), '..', 'src'))\n", - "from pymeasurement import Measurement as M, SigFig" + "df = pd.read_excel('_static\\Combustion_Lab_1920_Student_Data.xlsx')" ] }, { "cell_type": "code", - "execution_count": 106, + "execution_count": 42, "metadata": {}, "outputs": [ { @@ -413,28 +408,16 @@ "22 60.6 +/- 0.1 ºC " ] }, - "execution_count": 106, + "execution_count": 42, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "def convert(sample, uncertainty=None, units='', analog=False, digital=False, constant=False, u=None, a=False, d=False, un='', decimals=None):\n", - " if u is not None:\n", - " uncertainty = u\n", - " if a:\n", - " analog = a\n", - " if d:\n", - " digital = d\n", - " if un:\n", - " units = un\n", - " \n", - " return M(SigFig(str(sample), decimals=-decimals if decimals is not None else None), uncertainty=str(uncertainty) if uncertainty is not None else None, precision=float('inf') if constant else None, units=units, analog=analog, digital=digital)\n", - "\n", "decimals = [3, 3, 3, 1, 1]\n", "units = ['g', 'g', 'g', 'ºC', 'ºC']\n", "for i in range(5):\n", - " df.iloc[:, i + 3] = df.iloc[:, i + 3].apply(convert, d=True, un=units[i], decimals=decimals[i])\n", + " df.iloc[:, i + 3] = M.importColumn(df.iloc[:, i + 3], d=True, un=units[i], decimals=decimals[i])\n", "\n", "\n", "df" @@ -442,7 +425,7 @@ }, { "cell_type": "code", - "execution_count": 107, + "execution_count": 43, "metadata": {}, "outputs": [], "source": [ @@ -452,27 +435,28 @@ }, { "cell_type": "code", - "execution_count": 108, + "execution_count": 44, "metadata": {}, "outputs": [], "source": [ - "df['Q of H2O (kJ)'] = df['Mass of water (+/- 0.001 g)'] * heat_capacity * (df['Final temperature (+/- 0.1 ºC)'] - df['Initial temperature (+/- 0.1 ºC)']) * j_to_kj" + "results_df_1 = df.iloc[:, 0:3]\n", + "results_df_1['Q of H2O (kJ)'] = df['Mass of water (+/- 0.001 g)'] * heat_capacity * (df['Final temperature (+/- 0.1 ºC)'] - df['Initial temperature (+/- 0.1 ºC)']) * j_to_kj" ] }, { "cell_type": "code", - "execution_count": 109, + "execution_count": 45, "metadata": {}, "outputs": [], "source": [ - "df['Mass of Alcohol (g)'] = df['Initial mass of spirit burner (+/- 0.001 g)'] - df['Final mass of spirit burner (+/- 0.001 g)']\n", - "molar_masses = {'Ethanol': M.fromStr('46.08c g/mol'), 'Propan-1-ol' :M.fromStr('60.11c g/mol'), 'Butan-1-ol': M.fromStr('74.14c g/mol'), 'Pentan-1-ol': M.fromStr('86.17c g/mol')}\n", - "df['Molar mass of Alcohol (g/mol)'] = df.apply(lambda x: x['Mass of Alcohol (g)'] / molar_masses[x['Alcohol tested']], axis=1)" + "results_df_1['Mass of Alcohol (g)'] = df['Initial mass of spirit burner (+/- 0.001 g)'] - df['Final mass of spirit burner (+/- 0.001 g)']\n", + "molar_masses = {'Ethanol': M.fromStr('46.08c g/mol'), 'Propan-1-ol': M.fromStr('60.11c g/mol'), 'Butan-1-ol': M.fromStr('74.14c g/mol'), 'Pentan-1-ol': M.fromStr('88.17c g/mol')}\n", + "results_df_1['Molar Mass of Alcohol (g/mol)'] = results_df_1.apply(lambda x: x['Mass of Alcohol (g)'] / molar_masses[x['Alcohol tested']], axis=1)" ] }, { "cell_type": "code", - "execution_count": 110, + "execution_count": 46, "metadata": {}, "outputs": [ { @@ -499,14 +483,9 @@ " Alcohol tested\n", " # of carbons\n", " Trial\n", - " Mass of water (+/- 0.001 g)\n", - " Initial mass of spirit burner (+/- 0.001 g)\n", - " Final mass of spirit burner (+/- 0.001 g)\n", - " Initial temperature (+/- 0.1 ºC)\n", - " Final temperature (+/- 0.1 ºC)\n", " Q of H2O (kJ)\n", " Mass of Alcohol (g)\n", - " Molar mass of Alcohol (g/mol)\n", + " Molar Mass of Alcohol (g/mol)\n", " Molar Enthalpy of Combustion (kJ/mol)\n", " \n", " \n", @@ -516,11 +495,6 @@ " Ethanol\n", " 2\n", " 1\n", - " 78.701 +/- 0.001 g\n", - " 110.759 +/- 0.001 g\n", - " 109.689 +/- 0.001 g\n", - " 21.7 +/- 0.1 ºC\n", - " 70.9 +/- 0.1 ºC\n", " 16.2 +/- 0.41% kJ\n", " 1.070 +/- 0.002 g\n", " 0.02322 +/- 0.19% mol\n", @@ -531,11 +505,6 @@ " Ethanol\n", " 2\n", " 2\n", - " 78.866 +/- 0.001 g\n", - " 102.941 +/- 0.001 g\n", - " 101.530 +/- 0.001 g\n", - " 20.7 +/- 0.1 ºC\n", - " 73.2 +/- 0.1 ºC\n", " 17.3 +/- 0.38% kJ\n", " 1.411 +/- 0.002 g\n", " 0.03062 +/- 0.14% mol\n", @@ -546,11 +515,6 @@ " Ethanol\n", " 2\n", " 3\n", - " 72.259 +/- 0.001 g\n", - " 99.972 +/- 0.001 g\n", - " 98.642 +/- 0.001 g\n", - " 22.7 +/- 0.1 ºC\n", - " 76.4 +/- 0.1 ºC\n", " 16.2 +/- 0.37% kJ\n", " 1.330 +/- 0.002 g\n", " 0.02886 +/- 0.15% mol\n", @@ -561,11 +525,6 @@ " Ethanol\n", " 2\n", " 4\n", - " 76.301 +/- 0.001 g\n", - " 98.581 +/- 0.001 g\n", - " 97.084 +/- 0.001 g\n", - " 22.1 +/- 0.1 ºC\n", - " 71.3 +/- 0.1 ºC\n", " 15.7 +/- 0.41% kJ\n", " 1.497 +/- 0.002 g\n", " 0.03249 +/- 0.13% mol\n", @@ -576,11 +535,6 @@ " Ethanol\n", " 2\n", " 5\n", - " 75.239 +/- 0.001 g\n", - " 115.399 +/- 0.001 g\n", - " 113.309 +/- 0.001 g\n", - " 20.4 +/- 0.1 ºC\n", - " 72.0 +/- 0.1 ºC\n", " 16.2 +/- 0.39% kJ\n", " 2.090 +/- 0.002 g\n", " 0.04536 +/- 0.096% mol\n", @@ -591,11 +545,6 @@ " Ethanol\n", " 2\n", " 6\n", - " 70.421 +/- 0.001 g\n", - " 106.816 +/- 0.001 g\n", - " 104.597 +/- 0.001 g\n", - " 22.0 +/- 0.1 ºC\n", - " 74.8 +/- 0.1 ºC\n", " 15.5 +/- 0.38% kJ\n", " 2.219 +/- 0.002 g\n", " 0.04816 +/- 0.090% mol\n", @@ -606,11 +555,6 @@ " Propan-1-ol\n", " 3\n", " 1\n", - " 71.624 +/- 0.001 g\n", - " 107.374 +/- 0.001 g\n", - " 106.277 +/- 0.001 g\n", - " 19.2 +/- 0.1 ºC\n", - " 68.5 +/- 0.1 ºC\n", " 14.8 +/- 0.41% kJ\n", " 1.097 +/- 0.002 g\n", " 0.01825 +/- 0.18% mol\n", @@ -621,11 +565,6 @@ " Propan-1-ol\n", " 3\n", " 2\n", - " 64.813 +/- 0.001 g\n", - " 111.018 +/- 0.001 g\n", - " 109.724 +/- 0.001 g\n", - " 21.0 +/- 0.1 ºC\n", - " 70.6 +/- 0.1 ºC\n", " 13.4 +/- 0.40% kJ\n", " 1.294 +/- 0.002 g\n", " 0.02153 +/- 0.15% mol\n", @@ -636,11 +575,6 @@ " Propan-1-ol\n", " 3\n", " 3\n", - " 68.053 +/- 0.001 g\n", - " 114.814 +/- 0.001 g\n", - " 113.206 +/- 0.001 g\n", - " 20.3 +/- 0.1 ºC\n", - " 70.7 +/- 0.1 ºC\n", " 14.3 +/- 0.40% kJ\n", " 1.608 +/- 0.002 g\n", " 0.02675 +/- 0.12% mol\n", @@ -651,11 +585,6 @@ " Propan-1-ol\n", " 3\n", " 4\n", - " 69.118 +/- 0.001 g\n", - " 106.248 +/- 0.001 g\n", - " 104.547 +/- 0.001 g\n", - " 20.7 +/- 0.1 ºC\n", - " 69.7 +/- 0.1 ºC\n", " 14.2 +/- 0.41% kJ\n", " 1.701 +/- 0.002 g\n", " 0.02830 +/- 0.12% mol\n", @@ -666,11 +595,6 @@ " Propan-1-ol\n", " 3\n", " 5\n", - " 68.542 +/- 0.001 g\n", - " 83.423 +/- 0.001 g\n", - " 80.625 +/- 0.001 g\n", - " 20.9 +/- 0.1 ºC\n", - " 70.7 +/- 0.1 ºC\n", " 14.3 +/- 0.40% kJ\n", " 2.798 +/- 0.002 g\n", " 0.04655 +/- 0.071% mol\n", @@ -681,11 +605,6 @@ " Propan-1-ol\n", " 3\n", " 6\n", - " 68.721 +/- 0.001 g\n", - " 89.714 +/- 0.001 g\n", - " 86.186 +/- 0.001 g\n", - " 22.1 +/- 0.1 ºC\n", - " 77.6 +/- 0.1 ºC\n", " 15.9 +/- 0.36% kJ\n", " 3.528 +/- 0.002 g\n", " 0.05869 +/- 0.057% mol\n", @@ -696,11 +615,6 @@ " Butan-1-ol\n", " 4\n", " 1\n", - " 67.828 +/- 0.001 g\n", - " 110.054 +/- 0.001 g\n", - " 108.840 +/- 0.001 g\n", - " 21.2 +/- 0.1 ºC\n", - " 69.3 +/- 0.1 ºC\n", " 13.6 +/- 0.42% kJ\n", " 1.214 +/- 0.002 g\n", " 0.01637 +/- 0.16% mol\n", @@ -711,11 +625,6 @@ " Butan-1-ol\n", " 4\n", " 2\n", - " 67.570 +/- 0.001 g\n", - " 105.246 +/- 0.001 g\n", - " 104.014 +/- 0.001 g\n", - " 22.7 +/- 0.1 ºC\n", - " 70.4 +/- 0.1 ºC\n", " 13.5 +/- 0.42% kJ\n", " 1.232 +/- 0.002 g\n", " 0.01662 +/- 0.16% mol\n", @@ -726,11 +635,6 @@ " Butan-1-ol\n", " 4\n", " 3\n", - " 74.844 +/- 0.001 g\n", - " 116.084 +/- 0.001 g\n", - " 114.649 +/- 0.001 g\n", - " 20.4 +/- 0.1 ºC\n", - " 66.3 +/- 0.1 ºC\n", " 14.4 +/- 0.44% kJ\n", " 1.435 +/- 0.002 g\n", " 0.01936 +/- 0.14% mol\n", @@ -741,11 +645,6 @@ " Butan-1-ol\n", " 4\n", " 4\n", - " 61.214 +/- 0.001 g\n", - " 103.961 +/- 0.001 g\n", - " 102.606 +/- 0.001 g\n", - " 22.0 +/- 0.1 ºC\n", - " 73.6 +/- 0.1 ºC\n", " 13.2 +/- 0.39% kJ\n", " 1.355 +/- 0.002 g\n", " 0.01828 +/- 0.15% mol\n", @@ -756,11 +655,6 @@ " Butan-1-ol\n", " 4\n", " 5\n", - " 70.545 +/- 0.001 g\n", - " 112.693 +/- 0.001 g\n", - " 111.043 +/- 0.001 g\n", - " 22.8 +/- 0.1 ºC\n", - " 75.6 +/- 0.1 ºC\n", " 15.6 +/- 0.38% kJ\n", " 1.650 +/- 0.002 g\n", " 0.02226 +/- 0.12% mol\n", @@ -771,197 +665,92 @@ " Pentan-1-ol\n", " 5\n", " 1\n", - " 70.119 +/- 0.001 g\n", - " 113.027 +/- 0.001 g\n", - " 112.175 +/- 0.001 g\n", - " 22.1 +/- 0.1 ºC\n", - " 73.1 +/- 0.1 ºC\n", " 14.9 +/- 0.39% kJ\n", " 0.852 +/- 0.002 g\n", - " 0.00989 +/- 0.23% mol\n", - " -1.51E+3 +/- 0.63% kJ/mol\n", + " 0.00966 +/- 0.23% mol\n", + " -1.55E+3 +/- 0.63% kJ/mol\n", " \n", " \n", " 18\n", " Pentan-1-ol\n", " 5\n", " 2\n", - " 141.762 +/- 0.001 g\n", - " 110.779 +/- 0.001 g\n", - " 109.117 +/- 0.001 g\n", - " 21.6 +/- 0.1 ºC\n", - " 69.6 +/- 0.1 ºC\n", " 28.4 +/- 0.42% kJ\n", " 1.662 +/- 0.002 g\n", - " 0.01929 +/- 0.12% mol\n", - " -1.47E+3 +/- 0.54% kJ/mol\n", + " 0.01885 +/- 0.12% mol\n", + " -1.51E+3 +/- 0.54% kJ/mol\n", " \n", " \n", " 19\n", " Pentan-1-ol\n", " 5\n", " 3\n", - " 64.175 +/- 0.001 g\n", - " 113.924 +/- 0.001 g\n", - " 113.019 +/- 0.001 g\n", - " 22.2 +/- 0.1 ºC\n", - " 76.7 +/- 0.1 ºC\n", " 14.6 +/- 0.37% kJ\n", " 0.905 +/- 0.002 g\n", - " 0.0105 +/- 0.22% mol\n", - " -1.39E+3 +/- 0.59% kJ/mol\n", + " 0.0103 +/- 0.22% mol\n", + " -1.42E+3 +/- 0.59% kJ/mol\n", " \n", " \n", " 20\n", " Pentan-1-ol\n", " 5\n", " 4\n", - " 62.989 +/- 0.001 g\n", - " 112.782 +/- 0.001 g\n", - " 111.609 +/- 0.001 g\n", - " 20.7 +/- 0.1 ºC\n", - " 71.8 +/- 0.1 ºC\n", " 13.5 +/- 0.39% kJ\n", " 1.173 +/- 0.002 g\n", - " 0.01361 +/- 0.17% mol\n", - " -988 +/- 0.56% kJ/mol\n", + " 0.01330 +/- 0.17% mol\n", + " -1.01E+3 +/- 0.56% kJ/mol\n", " \n", " \n", " 21\n", " Pentan-1-ol\n", " 5\n", " 5\n", - " 70.469 +/- 0.001 g\n", - " 112.166 +/- 0.001 g\n", - " 110.767 +/- 0.001 g\n", - " 20.0 +/- 0.1 ºC\n", - " 71.6 +/- 0.1 ºC\n", " 15.2 +/- 0.39% kJ\n", " 1.399 +/- 0.002 g\n", - " 0.01624 +/- 0.14% mol\n", - " -936 +/- 0.53% kJ/mol\n", + " 0.01587 +/- 0.14% mol\n", + " -958 +/- 0.53% kJ/mol\n", " \n", " \n", " 22\n", " Pentan-1-ol\n", " 5\n", " 6\n", - " 70.168 +/- 0.001 g\n", - " 103.432 +/- 0.001 g\n", - " 102.250 +/- 0.001 g\n", - " 20.3 +/- 0.1 ºC\n", - " 60.6 +/- 0.1 ºC\n", " 11.8 +/- 0.50% kJ\n", " 1.182 +/- 0.002 g\n", - " 0.01372 +/- 0.17% mol\n", - " -862 +/- 0.67% kJ/mol\n", + " 0.01341 +/- 0.17% mol\n", + " -882 +/- 0.67% kJ/mol\n", " \n", " \n", "\n", "" ], "text/plain": [ - " Alcohol tested # of carbons Trial Mass of water (+/- 0.001 g) \\\n", - "0 Ethanol 2 1 78.701 +/- 0.001 g \n", - "1 Ethanol 2 2 78.866 +/- 0.001 g \n", - "2 Ethanol 2 3 72.259 +/- 0.001 g \n", - "3 Ethanol 2 4 76.301 +/- 0.001 g \n", - "4 Ethanol 2 5 75.239 +/- 0.001 g \n", - "5 Ethanol 2 6 70.421 +/- 0.001 g \n", - "6 Propan-1-ol 3 1 71.624 +/- 0.001 g \n", - "7 Propan-1-ol 3 2 64.813 +/- 0.001 g \n", - "8 Propan-1-ol 3 3 68.053 +/- 0.001 g \n", - "9 Propan-1-ol 3 4 69.118 +/- 0.001 g \n", - "10 Propan-1-ol 3 5 68.542 +/- 0.001 g \n", - "11 Propan-1-ol 3 6 68.721 +/- 0.001 g \n", - "12 Butan-1-ol 4 1 67.828 +/- 0.001 g \n", - "13 Butan-1-ol 4 2 67.570 +/- 0.001 g \n", - "14 Butan-1-ol 4 3 74.844 +/- 0.001 g \n", - "15 Butan-1-ol 4 4 61.214 +/- 0.001 g \n", - "16 Butan-1-ol 4 5 70.545 +/- 0.001 g \n", - "17 Pentan-1-ol 5 1 70.119 +/- 0.001 g \n", - "18 Pentan-1-ol 5 2 141.762 +/- 0.001 g \n", - "19 Pentan-1-ol 5 3 64.175 +/- 0.001 g \n", - "20 Pentan-1-ol 5 4 62.989 +/- 0.001 g \n", - "21 Pentan-1-ol 5 5 70.469 +/- 0.001 g \n", - "22 Pentan-1-ol 5 6 70.168 +/- 0.001 g \n", + " Alcohol tested # of carbons Trial Q of H2O (kJ) Mass of Alcohol (g) \\\n", + "0 Ethanol 2 1 16.2 +/- 0.41% kJ 1.070 +/- 0.002 g \n", + "1 Ethanol 2 2 17.3 +/- 0.38% kJ 1.411 +/- 0.002 g \n", + "2 Ethanol 2 3 16.2 +/- 0.37% kJ 1.330 +/- 0.002 g \n", + "3 Ethanol 2 4 15.7 +/- 0.41% kJ 1.497 +/- 0.002 g \n", + "4 Ethanol 2 5 16.2 +/- 0.39% kJ 2.090 +/- 0.002 g \n", + "5 Ethanol 2 6 15.5 +/- 0.38% kJ 2.219 +/- 0.002 g \n", + "6 Propan-1-ol 3 1 14.8 +/- 0.41% kJ 1.097 +/- 0.002 g \n", + "7 Propan-1-ol 3 2 13.4 +/- 0.40% kJ 1.294 +/- 0.002 g \n", + "8 Propan-1-ol 3 3 14.3 +/- 0.40% kJ 1.608 +/- 0.002 g \n", + "9 Propan-1-ol 3 4 14.2 +/- 0.41% kJ 1.701 +/- 0.002 g \n", + "10 Propan-1-ol 3 5 14.3 +/- 0.40% kJ 2.798 +/- 0.002 g \n", + "11 Propan-1-ol 3 6 15.9 +/- 0.36% kJ 3.528 +/- 0.002 g \n", + "12 Butan-1-ol 4 1 13.6 +/- 0.42% kJ 1.214 +/- 0.002 g \n", + "13 Butan-1-ol 4 2 13.5 +/- 0.42% kJ 1.232 +/- 0.002 g \n", + "14 Butan-1-ol 4 3 14.4 +/- 0.44% kJ 1.435 +/- 0.002 g \n", + "15 Butan-1-ol 4 4 13.2 +/- 0.39% kJ 1.355 +/- 0.002 g \n", + "16 Butan-1-ol 4 5 15.6 +/- 0.38% kJ 1.650 +/- 0.002 g \n", + "17 Pentan-1-ol 5 1 14.9 +/- 0.39% kJ 0.852 +/- 0.002 g \n", + "18 Pentan-1-ol 5 2 28.4 +/- 0.42% kJ 1.662 +/- 0.002 g \n", + "19 Pentan-1-ol 5 3 14.6 +/- 0.37% kJ 0.905 +/- 0.002 g \n", + "20 Pentan-1-ol 5 4 13.5 +/- 0.39% kJ 1.173 +/- 0.002 g \n", + "21 Pentan-1-ol 5 5 15.2 +/- 0.39% kJ 1.399 +/- 0.002 g \n", + "22 Pentan-1-ol 5 6 11.8 +/- 0.50% kJ 1.182 +/- 0.002 g \n", "\n", - " Initial mass of spirit burner (+/- 0.001 g) \\\n", - "0 110.759 +/- 0.001 g \n", - "1 102.941 +/- 0.001 g \n", - "2 99.972 +/- 0.001 g \n", - "3 98.581 +/- 0.001 g \n", - "4 115.399 +/- 0.001 g \n", - "5 106.816 +/- 0.001 g \n", - "6 107.374 +/- 0.001 g \n", - "7 111.018 +/- 0.001 g \n", - "8 114.814 +/- 0.001 g \n", - "9 106.248 +/- 0.001 g \n", - "10 83.423 +/- 0.001 g \n", - "11 89.714 +/- 0.001 g \n", - "12 110.054 +/- 0.001 g \n", - "13 105.246 +/- 0.001 g \n", - "14 116.084 +/- 0.001 g \n", - "15 103.961 +/- 0.001 g \n", - "16 112.693 +/- 0.001 g \n", - "17 113.027 +/- 0.001 g \n", - "18 110.779 +/- 0.001 g \n", - "19 113.924 +/- 0.001 g \n", - "20 112.782 +/- 0.001 g \n", - "21 112.166 +/- 0.001 g \n", - "22 103.432 +/- 0.001 g \n", - "\n", - " Final mass of spirit burner (+/- 0.001 g) Initial temperature (+/- 0.1 ºC) \\\n", - "0 109.689 +/- 0.001 g 21.7 +/- 0.1 ºC \n", - "1 101.530 +/- 0.001 g 20.7 +/- 0.1 ºC \n", - "2 98.642 +/- 0.001 g 22.7 +/- 0.1 ºC \n", - "3 97.084 +/- 0.001 g 22.1 +/- 0.1 ºC \n", - "4 113.309 +/- 0.001 g 20.4 +/- 0.1 ºC \n", - "5 104.597 +/- 0.001 g 22.0 +/- 0.1 ºC \n", - "6 106.277 +/- 0.001 g 19.2 +/- 0.1 ºC \n", - "7 109.724 +/- 0.001 g 21.0 +/- 0.1 ºC \n", - "8 113.206 +/- 0.001 g 20.3 +/- 0.1 ºC \n", - "9 104.547 +/- 0.001 g 20.7 +/- 0.1 ºC \n", - "10 80.625 +/- 0.001 g 20.9 +/- 0.1 ºC \n", - "11 86.186 +/- 0.001 g 22.1 +/- 0.1 ºC \n", - "12 108.840 +/- 0.001 g 21.2 +/- 0.1 ºC \n", - "13 104.014 +/- 0.001 g 22.7 +/- 0.1 ºC \n", - "14 114.649 +/- 0.001 g 20.4 +/- 0.1 ºC \n", - "15 102.606 +/- 0.001 g 22.0 +/- 0.1 ºC \n", - "16 111.043 +/- 0.001 g 22.8 +/- 0.1 ºC \n", - "17 112.175 +/- 0.001 g 22.1 +/- 0.1 ºC \n", - "18 109.117 +/- 0.001 g 21.6 +/- 0.1 ºC \n", - "19 113.019 +/- 0.001 g 22.2 +/- 0.1 ºC \n", - "20 111.609 +/- 0.001 g 20.7 +/- 0.1 ºC \n", - "21 110.767 +/- 0.001 g 20.0 +/- 0.1 ºC \n", - "22 102.250 +/- 0.001 g 20.3 +/- 0.1 ºC \n", - "\n", - " Final temperature (+/- 0.1 ºC) Q of H2O (kJ) Mass of Alcohol (g) \\\n", - "0 70.9 +/- 0.1 ºC 16.2 +/- 0.41% kJ 1.070 +/- 0.002 g \n", - "1 73.2 +/- 0.1 ºC 17.3 +/- 0.38% kJ 1.411 +/- 0.002 g \n", - "2 76.4 +/- 0.1 ºC 16.2 +/- 0.37% kJ 1.330 +/- 0.002 g \n", - "3 71.3 +/- 0.1 ºC 15.7 +/- 0.41% kJ 1.497 +/- 0.002 g \n", - "4 72.0 +/- 0.1 ºC 16.2 +/- 0.39% kJ 2.090 +/- 0.002 g \n", - "5 74.8 +/- 0.1 ºC 15.5 +/- 0.38% kJ 2.219 +/- 0.002 g \n", - "6 68.5 +/- 0.1 ºC 14.8 +/- 0.41% kJ 1.097 +/- 0.002 g \n", - "7 70.6 +/- 0.1 ºC 13.4 +/- 0.40% kJ 1.294 +/- 0.002 g \n", - "8 70.7 +/- 0.1 ºC 14.3 +/- 0.40% kJ 1.608 +/- 0.002 g \n", - "9 69.7 +/- 0.1 ºC 14.2 +/- 0.41% kJ 1.701 +/- 0.002 g \n", - "10 70.7 +/- 0.1 ºC 14.3 +/- 0.40% kJ 2.798 +/- 0.002 g \n", - "11 77.6 +/- 0.1 ºC 15.9 +/- 0.36% kJ 3.528 +/- 0.002 g \n", - "12 69.3 +/- 0.1 ºC 13.6 +/- 0.42% kJ 1.214 +/- 0.002 g \n", - "13 70.4 +/- 0.1 ºC 13.5 +/- 0.42% kJ 1.232 +/- 0.002 g \n", - "14 66.3 +/- 0.1 ºC 14.4 +/- 0.44% kJ 1.435 +/- 0.002 g \n", - "15 73.6 +/- 0.1 ºC 13.2 +/- 0.39% kJ 1.355 +/- 0.002 g \n", - "16 75.6 +/- 0.1 ºC 15.6 +/- 0.38% kJ 1.650 +/- 0.002 g \n", - "17 73.1 +/- 0.1 ºC 14.9 +/- 0.39% kJ 0.852 +/- 0.002 g \n", - "18 69.6 +/- 0.1 ºC 28.4 +/- 0.42% kJ 1.662 +/- 0.002 g \n", - "19 76.7 +/- 0.1 ºC 14.6 +/- 0.37% kJ 0.905 +/- 0.002 g \n", - "20 71.8 +/- 0.1 ºC 13.5 +/- 0.39% kJ 1.173 +/- 0.002 g \n", - "21 71.6 +/- 0.1 ºC 15.2 +/- 0.39% kJ 1.399 +/- 0.002 g \n", - "22 60.6 +/- 0.1 ºC 11.8 +/- 0.50% kJ 1.182 +/- 0.002 g \n", - "\n", - " Molar mass of Alcohol (g/mol) Molar Enthalpy of Combustion (kJ/mol) \n", + " Molar Mass of Alcohol (g/mol) Molar Enthalpy of Combustion (kJ/mol) \n", "0 0.02322 +/- 0.19% mol -697 +/- 0.59% kJ/mol \n", "1 0.03062 +/- 0.14% mol -565 +/- 0.52% kJ/mol \n", "2 0.02886 +/- 0.15% mol -562 +/- 0.52% kJ/mol \n", @@ -979,46 +768,753 @@ "14 0.01936 +/- 0.14% mol -742 +/- 0.58% kJ/mol \n", "15 0.01828 +/- 0.15% mol -722 +/- 0.54% kJ/mol \n", "16 0.02226 +/- 0.12% mol -700 +/- 0.50% kJ/mol \n", - "17 0.00989 +/- 0.23% mol -1.51E+3 +/- 0.63% kJ/mol \n", - "18 0.01929 +/- 0.12% mol -1.47E+3 +/- 0.54% kJ/mol \n", - "19 0.0105 +/- 0.22% mol -1.39E+3 +/- 0.59% kJ/mol \n", - "20 0.01361 +/- 0.17% mol -988 +/- 0.56% kJ/mol \n", - "21 0.01624 +/- 0.14% mol -936 +/- 0.53% kJ/mol \n", - "22 0.01372 +/- 0.17% mol -862 +/- 0.67% kJ/mol " + "17 0.00966 +/- 0.23% mol -1.55E+3 +/- 0.63% kJ/mol \n", + "18 0.01885 +/- 0.12% mol -1.51E+3 +/- 0.54% kJ/mol \n", + "19 0.0103 +/- 0.22% mol -1.42E+3 +/- 0.59% kJ/mol \n", + "20 0.01330 +/- 0.17% mol -1.01E+3 +/- 0.56% kJ/mol \n", + "21 0.01587 +/- 0.14% mol -958 +/- 0.53% kJ/mol \n", + "22 0.01341 +/- 0.17% mol -882 +/- 0.67% kJ/mol " ] }, - "execution_count": 110, + "execution_count": 46, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "df['Molar Enthalpy of Combustion (kJ/mol)'] = - df['Q of H2O (kJ)'] / df['Molar mass of Alcohol (g/mol)']\n", + "results_df_1['Molar Enthalpy of Combustion (kJ/mol)'] = - results_df_1['Q of H2O (kJ)'] / results_df_1['Molar Mass of Alcohol (g/mol)']\n", "\n", - "df" + "results_df_1" + ] + }, + { + "cell_type": "code", + "execution_count": 47, + "metadata": {}, + "outputs": [ + { + "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", + "
AlcoholAverage Molar Enthalpy of Combustion (kJ/mol)Accepted Molar Enthalpy of Combustion (kJ/mol)Percent Error (%)
0Ethanol-498.0 +/- 2E+1% kJ/mol-1367 kJ/mol63.57 +/- 9%
1Propan-1-ol-507.9 +/- 2E+1% kJ/mol-2021 kJ/mol74.869 +/- 7%
2Butan-1-ol-761.5 +/- 4% kJ/mol-2676 kJ/mol71.543 +/- 1.6%
3Pentan-1-ol-1222 +/- 1E+1% kJ/mol-3329 kJ/mol63.30 +/- 6%
\n", + "
" + ], + "text/plain": [ + " Alcohol Average Molar Enthalpy of Combustion (kJ/mol) \\\n", + "0 Ethanol -498.0 +/- 2E+1% kJ/mol \n", + "1 Propan-1-ol -507.9 +/- 2E+1% kJ/mol \n", + "2 Butan-1-ol -761.5 +/- 4% kJ/mol \n", + "3 Pentan-1-ol -1222 +/- 1E+1% kJ/mol \n", + "\n", + " Accepted Molar Enthalpy of Combustion (kJ/mol) Percent Error (%) \n", + "0 -1367 kJ/mol 63.57 +/- 9% \n", + "1 -2021 kJ/mol 74.869 +/- 7% \n", + "2 -2676 kJ/mol 71.543 +/- 1.6% \n", + "3 -3329 kJ/mol 63.30 +/- 6% " + ] + }, + "execution_count": 47, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "results_df_2 = pd.DataFrame()\n", + "results_df_2['Alcohol'] = results_df_1['Alcohol tested'].unique()\n", + "grouped_data = list(results_df_1.groupby('Alcohol tested')['Molar Enthalpy of Combustion (kJ/mol)'])\n", + "grouped_data_dict = {i[0]: i[1] for i in grouped_data}\n", + "results_df_2['Average Molar Enthalpy of Combustion (kJ/mol)'] = [M.average(list(grouped_data_dict[i])).percent() for i in results_df_2['Alcohol']]\n", + "results_df_2['Accepted Molar Enthalpy of Combustion (kJ/mol)'] = [M.fromStr('-1367c kJ/mol'), M.fromStr('-2021c kJ/mol'), M.fromStr('-2676c kJ/mol'), M.fromStr('-3329c kJ/mol')]\n", + "results_df_2['Percent Error (%)'] = results_df_2.apply(lambda x: ((x['Accepted Molar Enthalpy of Combustion (kJ/mol)'] - x['Average Molar Enthalpy of Combustion (kJ/mol)']) * 100 / x['Accepted Molar Enthalpy of Combustion (kJ/mol)']), axis=1)\n", + "\n", + "results_df_2" ] }, { "cell_type": "code", - "execution_count": 111, + "execution_count": 48, "metadata": {}, "outputs": [ { - "ename": "AttributeError", - "evalue": "type object 'Measurement' has no attribute 'average'", - "output_type": "error", - "traceback": [ - "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[1;31mAttributeError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[1;32mIn[111], line 3\u001b[0m\n\u001b[0;32m 1\u001b[0m results_df \u001b[39m=\u001b[39m pd\u001b[39m.\u001b[39mDataFrame()\n\u001b[0;32m 2\u001b[0m results_df[\u001b[39m'\u001b[39m\u001b[39mAlcohol\u001b[39m\u001b[39m'\u001b[39m] \u001b[39m=\u001b[39m df[\u001b[39m'\u001b[39m\u001b[39mAlcohol tested\u001b[39m\u001b[39m'\u001b[39m]\u001b[39m.\u001b[39munique()\n\u001b[1;32m----> 3\u001b[0m results_df[\u001b[39m'\u001b[39m\u001b[39mAverage Molar Enthalpy of Combustion (kJ/mol)\u001b[39m\u001b[39m'\u001b[39m] \u001b[39m=\u001b[39m M\u001b[39m.\u001b[39;49maverage(df\u001b[39m.\u001b[39mgroupby(\u001b[39m'\u001b[39m\u001b[39mAlcohol tested\u001b[39m\u001b[39m'\u001b[39m)[\u001b[39m'\u001b[39m\u001b[39mMolar Enthalpy of Combustion (kJ/mol)\u001b[39m\u001b[39m'\u001b[39m])\n", - "\u001b[1;31mAttributeError\u001b[0m: type object 'Measurement' has no attribute 'average'" - ] + "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", + " \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", + " \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", + "
Alcohol tested# of carbonsTrialQ of H2O (kJ)Q of H2O Percent Uncertainty (%)Mass of Alcohol (g)Mass of Alcohol Percent Uncertainty (%)Molar Mass of Alcohol (g/mol)Molar Mass of Alcohol Percent Uncertainty (%)Molar Enthalpy of Combustion (kJ/mol)Molar Enthalpy of Combustion Percent Uncertainty (%)
0Ethanol2116.20.411.0700.190.023220.19-6970.59
1Ethanol2217.30.381.4110.140.030620.14-5650.52
2Ethanol2316.20.371.3300.150.028860.15-5620.52
3Ethanol2415.70.411.4970.130.032490.13-4830.54
4Ethanol2516.20.392.0900.0960.045360.096-3580.48
5Ethanol2615.50.382.2190.0900.048160.090-3230.47
6Propan-1-ol3114.80.411.0970.180.018250.18-8090.59
7Propan-1-ol3213.40.401.2940.150.021530.15-6240.56
8Propan-1-ol3314.30.401.6080.120.026750.12-5360.52
9Propan-1-ol3414.20.411.7010.120.028300.12-5000.53
10Propan-1-ol3514.30.402.7980.0710.046550.071-3070.47
11Propan-1-ol3615.90.363.5280.0570.058690.057-2720.42
12Butan-1-ol4113.60.421.2140.160.016370.16-8330.58
13Butan-1-ol4213.50.421.2320.160.016620.16-8110.58
14Butan-1-ol4314.40.441.4350.140.019360.14-7420.58
15Butan-1-ol4413.20.391.3550.150.018280.15-7220.54
16Butan-1-ol4515.60.381.6500.120.022260.12-7000.50
17Pentan-1-ol5114.90.390.8520.230.009660.23-1.55E+30.63
18Pentan-1-ol5228.40.421.6620.120.018850.12-1.51E+30.54
19Pentan-1-ol5314.60.370.9050.220.01030.22-1.42E+30.59
20Pentan-1-ol5413.50.391.1730.170.013300.17-1.01E+30.56
21Pentan-1-ol5515.20.391.3990.140.015870.14-9580.53
22Pentan-1-ol5611.80.501.1820.170.013410.17-8820.67
\n", + "
" + ], + "text/plain": [ + " Alcohol tested # of carbons Trial Q of H2O (kJ) \\\n", + "0 Ethanol 2 1 16.2 \n", + "1 Ethanol 2 2 17.3 \n", + "2 Ethanol 2 3 16.2 \n", + "3 Ethanol 2 4 15.7 \n", + "4 Ethanol 2 5 16.2 \n", + "5 Ethanol 2 6 15.5 \n", + "6 Propan-1-ol 3 1 14.8 \n", + "7 Propan-1-ol 3 2 13.4 \n", + "8 Propan-1-ol 3 3 14.3 \n", + "9 Propan-1-ol 3 4 14.2 \n", + "10 Propan-1-ol 3 5 14.3 \n", + "11 Propan-1-ol 3 6 15.9 \n", + "12 Butan-1-ol 4 1 13.6 \n", + "13 Butan-1-ol 4 2 13.5 \n", + "14 Butan-1-ol 4 3 14.4 \n", + "15 Butan-1-ol 4 4 13.2 \n", + "16 Butan-1-ol 4 5 15.6 \n", + "17 Pentan-1-ol 5 1 14.9 \n", + "18 Pentan-1-ol 5 2 28.4 \n", + "19 Pentan-1-ol 5 3 14.6 \n", + "20 Pentan-1-ol 5 4 13.5 \n", + "21 Pentan-1-ol 5 5 15.2 \n", + "22 Pentan-1-ol 5 6 11.8 \n", + "\n", + " Q of H2O Percent Uncertainty (%) Mass of Alcohol (g) \\\n", + "0 0.41 1.070 \n", + "1 0.38 1.411 \n", + "2 0.37 1.330 \n", + "3 0.41 1.497 \n", + "4 0.39 2.090 \n", + "5 0.38 2.219 \n", + "6 0.41 1.097 \n", + "7 0.40 1.294 \n", + "8 0.40 1.608 \n", + "9 0.41 1.701 \n", + "10 0.40 2.798 \n", + "11 0.36 3.528 \n", + "12 0.42 1.214 \n", + "13 0.42 1.232 \n", + "14 0.44 1.435 \n", + "15 0.39 1.355 \n", + "16 0.38 1.650 \n", + "17 0.39 0.852 \n", + "18 0.42 1.662 \n", + "19 0.37 0.905 \n", + "20 0.39 1.173 \n", + "21 0.39 1.399 \n", + "22 0.50 1.182 \n", + "\n", + " Mass of Alcohol Percent Uncertainty (%) Molar Mass of Alcohol (g/mol) \\\n", + "0 0.19 0.02322 \n", + "1 0.14 0.03062 \n", + "2 0.15 0.02886 \n", + "3 0.13 0.03249 \n", + "4 0.096 0.04536 \n", + "5 0.090 0.04816 \n", + "6 0.18 0.01825 \n", + "7 0.15 0.02153 \n", + "8 0.12 0.02675 \n", + "9 0.12 0.02830 \n", + "10 0.071 0.04655 \n", + "11 0.057 0.05869 \n", + "12 0.16 0.01637 \n", + "13 0.16 0.01662 \n", + "14 0.14 0.01936 \n", + "15 0.15 0.01828 \n", + "16 0.12 0.02226 \n", + "17 0.23 0.00966 \n", + "18 0.12 0.01885 \n", + "19 0.22 0.0103 \n", + "20 0.17 0.01330 \n", + "21 0.14 0.01587 \n", + "22 0.17 0.01341 \n", + "\n", + " Molar Mass of Alcohol Percent Uncertainty (%) \\\n", + "0 0.19 \n", + "1 0.14 \n", + "2 0.15 \n", + "3 0.13 \n", + "4 0.096 \n", + "5 0.090 \n", + "6 0.18 \n", + "7 0.15 \n", + "8 0.12 \n", + "9 0.12 \n", + "10 0.071 \n", + "11 0.057 \n", + "12 0.16 \n", + "13 0.16 \n", + "14 0.14 \n", + "15 0.15 \n", + "16 0.12 \n", + "17 0.23 \n", + "18 0.12 \n", + "19 0.22 \n", + "20 0.17 \n", + "21 0.14 \n", + "22 0.17 \n", + "\n", + " Molar Enthalpy of Combustion (kJ/mol) \\\n", + "0 -697 \n", + "1 -565 \n", + "2 -562 \n", + "3 -483 \n", + "4 -358 \n", + "5 -323 \n", + "6 -809 \n", + "7 -624 \n", + "8 -536 \n", + "9 -500 \n", + "10 -307 \n", + "11 -272 \n", + "12 -833 \n", + "13 -811 \n", + "14 -742 \n", + "15 -722 \n", + "16 -700 \n", + "17 -1.55E+3 \n", + "18 -1.51E+3 \n", + "19 -1.42E+3 \n", + "20 -1.01E+3 \n", + "21 -958 \n", + "22 -882 \n", + "\n", + " Molar Enthalpy of Combustion Percent Uncertainty (%) \n", + "0 0.59 \n", + "1 0.52 \n", + "2 0.52 \n", + "3 0.54 \n", + "4 0.48 \n", + "5 0.47 \n", + "6 0.59 \n", + "7 0.56 \n", + "8 0.52 \n", + "9 0.53 \n", + "10 0.47 \n", + "11 0.42 \n", + "12 0.58 \n", + "13 0.58 \n", + "14 0.58 \n", + "15 0.54 \n", + "16 0.50 \n", + "17 0.63 \n", + "18 0.54 \n", + "19 0.59 \n", + "20 0.56 \n", + "21 0.53 \n", + "22 0.67 " + ] + }, + "execution_count": 48, + "metadata": {}, + "output_type": "execute_result" } ], "source": [ - "results_df = pd.DataFrame()\n", - "results_df['Alcohol'] = df['Alcohol tested'].unique()\n", - "results_df['Average Molar Enthalpy of Combustion (kJ/mol)'] = M.average(df.groupby('Alcohol tested')['Molar Enthalpy of Combustion (kJ/mol)'])" + "final_results_df_1 = results_df_1.copy()\n", + "for i in range(4):\n", + " M.exportColumn(final_results_df_1, results_df_1.iloc[:, i + 3])\n", + "\n", + "final_results_df_1" + ] + }, + { + "cell_type": "code", + "execution_count": 49, + "metadata": {}, + "outputs": [ + { + "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", + "
AlcoholAverage Molar Enthalpy of Combustion (kJ/mol)Average Molar Enthalpy of Combustion Percent Uncertainty (%)Accepted Molar Enthalpy of Combustion (kJ/mol)Percent Error (%)
0Ethanol-498.02E+1-136763.57
1Propan-1-ol-507.92E+1-202174.869
2Butan-1-ol-761.54-267671.543
3Pentan-1-ol-12221E+1-332963.30
\n", + "
" + ], + "text/plain": [ + " Alcohol Average Molar Enthalpy of Combustion (kJ/mol) \\\n", + "0 Ethanol -498.0 \n", + "1 Propan-1-ol -507.9 \n", + "2 Butan-1-ol -761.5 \n", + "3 Pentan-1-ol -1222 \n", + "\n", + " Average Molar Enthalpy of Combustion Percent Uncertainty (%) \\\n", + "0 2E+1 \n", + "1 2E+1 \n", + "2 4 \n", + "3 1E+1 \n", + "\n", + " Accepted Molar Enthalpy of Combustion (kJ/mol) Percent Error (%) \n", + "0 -1367 63.57 \n", + "1 -2021 74.869 \n", + "2 -2676 71.543 \n", + "3 -3329 63.30 " + ] + }, + "execution_count": 49, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "final_results_df_2 = results_df_2.copy()\n", + "for i in range(3):\n", + " M.exportColumn(final_results_df_2, results_df_2.iloc[:, i + 1], addUncertainty=i==0)\n", + "\n", + "final_results_df_2" ] } ], diff --git a/pyproject.toml b/pyproject.toml index 8c19504..4024ac7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "pymeasurement" -version = "1.0.7" +version = "1.0.8" authors = [ { name="Saptak Das", email="saptak.das625@gmail.com" }, ] diff --git a/src/pymeasurement/measurement.py b/src/pymeasurement/measurement.py index 6df8f8e..3d35aea 100644 --- a/src/pymeasurement/measurement.py +++ b/src/pymeasurement/measurement.py @@ -555,4 +555,83 @@ def average(measurements): """ avg_sample = Measurement.sum(measurements) / len(measurements) avg_uncertainty = (Measurement.max(measurements) - Measurement.min(measurements)).sample / (2 * math.sqrt(len(measurements))) - return Measurement(avg_sample.sample, uncertainty=avg_uncertainty, units=avg_sample.units) + return Measurement(avg_sample.sample, uncertainty=avg_uncertainty.value, units=avg_sample.units) + + def convert(sample, uncertainty=None, uncertaintyPercent=False, units='', analog=False, digital=False, constant=False, u=None, up=False, a=False, d=False, un='', decimals=None): + """ + Returns a Measurement object with the given sample, uncertainty, and units. + + :param sample: The sample of the Measurement object. + :type sample: float or str + :param uncertainty: The uncertainty of the Measurement object. + :type uncertainty: float or str + :param uncertaintyPercent: Whether the uncertainty is a percent. + :type uncertaintyPercent: bool + :param units: The units of the Measurement object. + :type units: str + :param analog: Whether the Measurement object is analog. + :type analog: bool + :param digital: Whether the Measurement object is digital. + :type digital: bool + :param constant: Whether the Measurement object is constant. + :type constant: bool + :param u: The uncertainty of the Measurement object. + :type u: float or str + :param up: Whether the uncertainty is a percent. + :type up: bool + :param a: Whether the Measurement object is analog. + :type a: bool + :param d: Whether the Measurement object is digital. + :type d: bool + :param un: The units of the Measurement object. + :type un: str + :param decimals: The number of decimals to round to. + :type decimals: int + """ + if u is not None: + uncertainty = u + if up: + uncertaintyPercent = up + if a: + analog = a + if d: + digital = d + if un: + units = un + + return Measurement(SigFig(str(sample), decimals=-decimals if decimals is not None else None), uncertaintyPercent=uncertaintyPercent, uncertainty=str(uncertainty) if uncertainty is not None else None, precision=float('inf') if constant else None, units=units, analog=analog, digital=digital) + + def importColumn(column, uncertaintyColumn=None, df=None, **kwargs): + """ + Convert a numeric Pandas DataFrame column to Measurement objects. + + :param column: The numeric Pandas DataFrame column. + :type column: pandas.core.series.Series + :param kwargs: Keyword arguments to pass to Measurement.convert. + :type kwargs: dict + """ + if uncertaintyColumn is None: + return column.apply(Measurement.convert, **kwargs) + else: + return df.apply(lambda x: Measurement.convert(x[column.name], u=x[uncertaintyColumn.name], **kwargs), axis=1) + + def exportColumn(savedf, column, addUncertainty=True, asPercent=True): + """ + Convert a Measurement Pandas DataFrame column to numeric values. + + :param savedf: The Pandas DataFrame to save to. + :type savedf: pandas.core.frame.DataFrame + :param column: The Measurement Pandas DataFrame column. + :type column: pandas.core.series.Series + :param addUncertainty: Whether to add the uncertainty to the DataFrame. + :type addUncertainty: bool + :param asPercent: Whether to add the uncertainty as a percent. + :type asPercent: bool + """ + savedf[column.name] = column.apply(lambda x: x.sample) + if addUncertainty: + label, units = (' ('.join(column.name.split(' (')[:-1]), ' (' + column.name.split(' (')[-1]) if ' (' in column.name else (column.name, '') + if asPercent: + savedf.insert(savedf.columns.get_loc(column.name) + 1, f'{label} Percent Uncertainty (%)', column.apply(lambda x: x.percent().uncertainty)) + else: + savedf.insert(savedf.columns.get_loc(column.name) + 1, f'{label} Absolute Uncertainty{units}', column.apply(lambda x: x.absolute().uncertainty))