From 4a987808fc720dd82e15ac385bf529d2b9703b93 Mon Sep 17 00:00:00 2001 From: rasmusvt Date: Thu, 7 Oct 2021 16:25:08 +0200 Subject: [PATCH] Update unit conversion functions --- beamtime/electrochemistry/__init__.py | 2 +- beamtime/electrochemistry/io.py | 226 +++++++++++------------ beamtime/electrochemistry/unit_tables.py | 53 ++++++ 3 files changed, 160 insertions(+), 121 deletions(-) create mode 100644 beamtime/electrochemistry/unit_tables.py diff --git a/beamtime/electrochemistry/__init__.py b/beamtime/electrochemistry/__init__.py index e0c4c87..0270f1d 100644 --- a/beamtime/electrochemistry/__init__.py +++ b/beamtime/electrochemistry/__init__.py @@ -1 +1 @@ -from . import io, plot +from . import io, plot, unit_tables diff --git a/beamtime/electrochemistry/io.py b/beamtime/electrochemistry/io.py index 86964ea..3e5bf02 100644 --- a/beamtime/electrochemistry/io.py +++ b/beamtime/electrochemistry/io.py @@ -1,10 +1,11 @@ import pandas as pd import numpy as np import matplotlib.pyplot as plt +import os -def read_battsmall(path): - ''' Reads BATTSMALL-data into a DataFrame. +def read_batsmall(path): + ''' Reads BATSMALL-data into a DataFrame. Input: path (required): string with path to datafile @@ -21,47 +22,46 @@ def read_battsmall(path): def read_neware(path, summary=False, active_material_weight=None, molecular_weight=None): - ''' Reads electrochemistry data, currently only from the Neware battery cycler. Will convert to .csv if the filetype is .xlsx, - which is the file format the Neware provides for the backup data. In this case it matters if summary is False or not. If file - type is .csv, it will just open the datafile and it does not matter if summary is False or not.''' - + ''' Reads electrochemistry data, currently only from the Neware battery cycler. Will convert to .csv if the filetype is .xlsx, + which is the file format the Neware provides for the backup data. In this case it matters if summary is False or not. If file + type is .csv, it will just open the datafile and it does not matter if summary is False or not.''' + from xlsx2csv import Xlsx2csv - # Convert from .xlsx to .csv to make readtime faster - if filename.split('.')[-1] == 'xlsx': - csv_details = ''.join(filename.split('.')[:-1]) + '_details.csv' - csv_summary = ''.join(filename.split('.')[:-1]) + '_summary.csv' + # Convert from .xlsx to .csv to make readtime faster + if path.split('.')[-1] == 'xlsx': + csv_details = ''.join(path.split('.')[:-1]) + '_details.csv' + csv_summary = ''.join(path.split('.')[:-1]) + '_summary.csv' - Xlsx2csv(filename, outputencoding="utf-8").convert(csv_summary, sheetid=3) - Xlsx2csv(filename, outputencoding="utf-8").convert(csv_details, sheetid=4) - - if summary: - df = pd.read_csv(csv_summary) - else: - df = pd.read_csv(csv_details) + if not os.path.isfile(csv_summary): + Xlsx2csv(path, outputencoding="utf-8").convert(csv_summary, sheetid=3) - elif filename.split('.')[-1] == 'csv': + if not os.path.isfile(csv_details): + Xlsx2csv(path, outputencoding="utf-8").convert(csv_details, sheetid=4) - df = pd.read_csv(filename) - - - return df + if summary: + df = pd.read_csv(csv_summary) + else: + df = pd.read_csv(csv_details) + + elif path.split('.')[-1] == 'csv': + df = pd.read_csv(path) + + + return df - -#def process_battsmall_data(df, t='ms', C='mAh/g', I='mA', U='V'): - -def process_battsmall_data(df, units=None, splice_cycles=None, molecular_weight=None): - ''' Takes BATTSMALL-data in the form of a DataFrame and cleans the data up and converts units into desired units. +def process_batsmall_data(df, units=None, splice_cycles=None, molecular_weight=None): + ''' Takes BATSMALL-data in the form of a DataFrame and cleans the data up and converts units into desired units. Splits up into individual charge and discharge DataFrames per cycle, and outputs a list where each element is a tuple with the Chg and DChg-data. E.g. cycles[10][0] gives the charge data for the 11th cycle. For this to work, the cycling program must be set to use the counter. Input: - df (required): A pandas DataFrame containing BATTSMALL-data, as obtained from read_battsmall(). + df (required): A pandas DataFrame containing BATSMALL-data, as obtained from read_batsmall(). t (optional): Unit for time data. Defaults to ms. C (optional): Unit for specific capacity. Defaults to mAh/g. I (optional): Unit for current. Defaults mA. @@ -71,32 +71,13 @@ def process_battsmall_data(df, units=None, splice_cycles=None, molecular_weight= cycles: A list with ''' - ######################### - #### UNIT CONVERSION #### - ######################### - # Complete the list of units - if not all are passed, then default value will be used - required_units = ['t', 'I', 'U', 'C'] - default_units = {'t': 'h', 'I': 'mA', 'U': 'V', 'C': 'mAh/g'} + # Complete set of new units and get the units used in the dataset, and convert values in the DataFrame from old to new. + new_units = set_units(units=units) + old_units = get_old_units(df, kind='batsmall') + df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='batsmall') - if not units: - units = default_units - - if units: - for unit in required_units: - if unit not in units.values(): - units[unit] = default_units[unit] - - - # Get the units used in the data set - t_prev = df.columns[0].split()[-1].strip('[]') - U_prev = df.columns[1].split()[-1].strip('[]') - I_prev = df.columns[2].split()[-1].strip('[]') - C_prev, m_prev = df.columns[4].split()[-1].strip('[]').split('/') - prev_units = {'t': t_prev, 'I': I_prev, 'U': U_prev, 'C': C_prev} - - # Convert all units to the desired units. - df = unit_conversion(df=df, units=units) + df.columns = ['TT', 'U', 'I', 'Z1', 'C', 'Comment'] # Replace NaN with empty string in the Comment-column and then remove all steps where the program changes - this is due to inconsistent values for current df[["Comment"]] = df[["Comment"]].fillna(value={'Comment': ''}) @@ -138,93 +119,98 @@ def process_neware_data(df, units=None, splice_cycles=None, active_material_weig #### UNIT CONVERSION #### ######################### + # Complete set of new units and get the units used in the dataset, and convert values in the DataFrame from old to new. + new_units = set_units(units=units) + old_units = get_old_units(df, kind='neware') + df = unit_conversion(df=df, new_units=new_units, old_units=old_units, kind='neware') + + + # if active_material_weight: + # df["SpecificCapacity(mAh/g)"] = df["Capacity(mAh)"] / (active_material_weight / 1000) + + # if molecular_weight: + # faradays_constant = 96485.3365 # [F] = C mol^-1 = As mol^-1 + # seconds_per_hour = 3600 # s h^-1 + # f = faradays_constant / seconds_per_hour * 1000.0 # [f] = mAh mol^-1 + + # df["IonsExtracted"] = (df["SpecificCapacity(mAh/g)"]*molecular_weight)/f + + + return df + + +def unit_conversion(df, new_units, old_units, kind): + from . import unit_tables + + if kind == 'batsmall': + + df["TT [{}]".format(old_units["time"])] = df["TT [{}]".format(old_units["time"])] * unit_tables.time()[old_units["time"]].loc[new_units['time']] + df["U [{}]".format(old_units["voltage"])] = df["U [{}]".format(old_units["voltage"])] * unit_tables.voltage()[old_units["voltage"]].loc[new_units['voltage']] + df["I [{}]".format(old_units["current"])] = df["I [{}]".format(old_units["current"])] * unit_tables.current()[old_units["current"]].loc[new_units['current']] + df["C [{}/{}]".format(old_units["capacity"], old_units["mass"])] = df["C [{}/{}]".format(old_units["capacity"], old_units["mass"])] * (unit_tables.capacity()[old_units["capacity"]].loc[new_units["capacity"]] / unit_tables.mass()[old_units["mass"]].loc[new_units["mass"]]) + + + if kind == 'neware': + df['Current({})'.format(old_units['current'])] = df['Current({})'.format(old_units['current'])] * unit_tables.current()[old_units['current']].loc[new_units['current']] + df['Voltage({})'.format(old_units['voltage'])] = df['Voltage({})'.format(old_units['voltage'])] * unit_tables.voltage()[old_units['voltage']].loc[new_units['voltage']] + df['Capacity({})'.format(old_units['capacity'])] = df['Capacity({})'.format(old_units['capacity'])] * unit_tables.capacity()[old_units['capacity']].loc[new_units['capacity']] + df['Energy({})'.format(old_units['energy'])] = df['Energy({})'.format(old_units['energy'])] * unit_tables.energy()[old_units['energy']].loc[new_units['energy']] + + df['RelativeTime({})'.format(new_units['time'])] = df.apply(lambda row : convert_time_string(row['Relative Time(h:min:s.ms)'], unit=new_units['time']), axis=1) + + return df + + +def set_units(units=None): + # Complete the list of units - if not all are passed, then default value will be used - required_units = ['t', 'I', 'U', 'C'] - default_units = {'t': 'h', 'I': 'mA', 'U': 'V', 'C': 'mAh/g'} + required_units = ['time', 'current', 'voltage', 'capacity', 'mass', 'energy'] + default_units = {'time': 'h', 'current': 'mA', 'voltage': 'V', 'capacity': 'mAh', 'mass': 'g', 'energy': 'mWh'} if not units: units = default_units if units: for unit in required_units: - if unit not in units.values(): + if unit not in units.keys(): units[unit] = default_units[unit] + + return units + + + +def get_old_units(df, kind): + if kind=='batsmall': + time = df.columns[0].split()[-1].strip('[]') + voltage = df.columns[1].split()[-1].strip('[]') + current = df.columns[2].split()[-1].strip('[]') + capacity, mass = df.columns[4].split()[-1].strip('[]').split('/') + old_units = {'time': time, 'current': current, 'voltage': voltage, 'capacity': capacity, 'mass': mass} - # Get the units used in the data set - t_prev = 's' # default in - U_prev = df.columns[1].split()[-1].strip('[]') - I_prev = df.columns[2].split()[-1].strip('[]') - C_prev, m_prev = df.columns[4].split()[-1].strip('[]').split('/') - prev_units = {'t': t_prev, 'I': I_prev, 'U': U_prev, 'C': C_prev} + if kind=='neware': + + for column in df.columns: + if 'Voltage' in column: + voltage = column.split('(')[-1].strip(')') + elif 'Current' in column: + current = column.split('(')[-1].strip(')') + elif 'Capacity' in column: + capacity = column.split('(')[-1].strip(')') + elif 'Energy' in column: + energy = column.split('(')[-1].strip(')') - # Convert all units to the desired units. - df = unit_conversion(df=df, units=units) - - - - if active_material_weight: - df["SpecificCapacity(mAh/g)"] = df["Capacity(mAh)"] / (active_material_weight / 1000) - - if molecular_weight: - faradays_constant = 96485.3365 # [F] = C mol^-1 = As mol^-1 - seconds_per_hour = 3600 # s h^-1 - f = faradays_constant / seconds_per_hour * 1000.0 # [f] = mAh mol^-1 - - df["IonsExtracted"] = (df["SpecificCapacity(mAh/g)"]*molecular_weight)/f - - -def unit_conversion(df, units, prev_units, kind): - - C, m = units['C'].split('/') - C_prev, m_prev = prev_units['C'].split('/') - - - # Define matrix for unit conversion for time - t_units_df = {'h': [1, 60, 3600, 3600000], 'min': [1/60, 1, 60, 60000], 's': [1/3600, 1/60, 1, 1000], 'ms': [1/3600000, 1/60000, 1/1000, 1]} - t_units_df = pd.DataFrame(t_units_df) - t_units_df.index = ['h', 'min', 's', 'ms'] - - # Define matrix for unit conversion for current - I_units_df = {'A': [1, 1000, 1000000], 'mA': [1/1000, 1, 1000], 'uA': [1/1000000, 1/1000, 1]} - I_units_df = pd.DataFrame(I_units_df) - I_units_df.index = ['A', 'mA', 'uA'] - - # Define matrix for unit conversion for voltage - U_units_df = {'V': [1, 1000, 1000000], 'mV': [1/1000, 1, 1000], 'uV': [1/1000000, 1/1000, 1]} - U_units_df = pd.DataFrame(U_units_df) - U_units_df.index = ['V', 'mV', 'uV'] - - # Define matrix for unit conversion for capacity - C_units_df = {'Ah': [1, 1000, 1000000], 'mAh': [1/1000, 1, 1000], 'uAh': [1/1000000, 1/1000, 1]} - C_units_df = pd.DataFrame(C_units_df) - C_units_df.index = ['Ah', 'mAh', 'uAh'] - - # Define matrix for unit conversion for capacity - m_units_df = {'kg': [1, 1000, 1000000, 1000000000], 'g': [1/1000, 1, 1000, 1000000], 'mg': [1/1000000, 1/1000, 1, 1000], 'ug': [1/1000000000, 1/1000000, 1/1000, 1]} - m_units_df = pd.DataFrame(m_units_df) - m_units_df.index = ['kg', 'g', 'mg', 'ug'] - - #print(df["TT [{}]".format(t_prev)]) - df["TT [{}]".format(t_prev)] = df["TT [{}]".format(t_prev)] * t_units_df[t_prev].loc[units['t']] - df["U [{}]".format(U_prev)] = df["U [{}]".format(U_prev)] * U_units_df[U_prev].loc[units['U']] - df["I [{}]".format(I_prev)] = df["I [{}]".format(I_prev)] * I_units_df[I_prev].loc[units['I']] - df["C [{}/{}]".format(C_prev, m_prev)] = df["C [{}/{}]".format(C_prev, m_prev)] * (C_units_df[C_prev].loc[C] / m_units_df[m_prev].loc[m]) - - df.columns = ['TT', 'U', 'I', 'Z1', 'C', 'Comment'] - - - - return df + old_units = {'voltage': voltage, 'current': current, 'capacity': capacity, 'energy': energy} + return old_units def convert_time_string(time_string, unit='ms'): ''' Convert time string from Neware-data with the format hh:mm:ss.xx to any given unit''' h, m, s = time_string.split(':') - ms = int(s)*1000 + int(m)*1000*60 + int(h)*1000*60*60 + ms = float(s)*1000 + int(m)*1000*60 + int(h)*1000*60*60 factors = {'ms': 1, 's': 1/1000, 'min': 1/(1000*60), 'h': 1/(1000*60*60)} diff --git a/beamtime/electrochemistry/unit_tables.py b/beamtime/electrochemistry/unit_tables.py new file mode 100644 index 0000000..c839b9b --- /dev/null +++ b/beamtime/electrochemistry/unit_tables.py @@ -0,0 +1,53 @@ +import pandas as pd + +def time(): + # Define matrix for unit conversion for time + time = {'h': [1, 60, 3600, 3600000], 'min': [1/60, 1, 60, 60000], 's': [1/3600, 1/60, 1, 1000], 'ms': [1/3600000, 1/60000, 1/1000, 1]} + time = pd.DataFrame(time) + time.index = ['h', 'min', 's', 'ms'] + + return time + +def current(): + # Define matrix for unit conversion for current + current = {'A': [1, 1000, 1000000], 'mA': [1/1000, 1, 1000], 'uA': [1/1000000, 1/1000, 1]} + current = pd.DataFrame(current) + current.index = ['A', 'mA', 'uA'] + + return current + +def voltage(): + # Define matrix for unit conversion for voltage + voltage = {'V': [1, 1000, 1000000], 'mV': [1/1000, 1, 1000], 'uV': [1/1000000, 1/1000, 1]} + voltage = pd.DataFrame(voltage) + voltage.index = ['V', 'mV', 'uV'] + + return voltage + +def capacity(): + # Define matrix for unit conversion for capacity + capacity = {'Ah': [1, 1000, 1000000], 'mAh': [1/1000, 1, 1000], 'uAh': [1/1000000, 1/1000, 1]} + capacity = pd.DataFrame(capacity) + capacity.index = ['Ah', 'mAh', 'uAh'] + + return capacity + +def mass(): + # Define matrix for unit conversion for capacity + mass = {'kg': [1, 1000, 1000000, 1000000000], 'g': [1/1000, 1, 1000, 1000000], 'mg': [1/1000000, 1/1000, 1, 1000], 'ug': [1/1000000000, 1/1000000, 1/1000, 1]} + mass = pd.DataFrame(mass) + mass.index = ['kg', 'g', 'mg', 'ug'] + + return mass + + +def energy(): + + energy = {'kWh': [1, 1000, 1000000], 'Wh': [1/1000, 1, 1000], 'mWh': [1/100000, 1/1000, 1]} + energy = pd.DataFrame(energy) + energy.index = ['kWh', 'Wh', 'mWh'] + + return energy + + +