<jupyter_start><jupyter_text>Storage operation disaggregatorA module to disaggregate energy storage operation by time scales.The disaggregation only requires the charging and discharging power profiles and is agnostic with respect to electricity prices or other drivers.  Installation```bashpip install storedisagg```  Example<jupyter_code>import sys, os
import matplotlib.pyplot as plt
import pandas as pd
from IPython.display import display

import storedisagg as std

%matplotlib inline<jupyter_output><empty_output><jupyter_text>Retrieve example dataExample data with 100 time slots is generated in the storedisagg.example.example_data module.<jupyter_code>df = std.get_example_data_100()

fig, ax = plt.subplots(1, 2)

dfplot = df.set_index('t').assign(Discharging=-df['dch'], Charging=df['chg'])
display(dfplot.head(7))

dfplot['Stored energy'] = dfplot[['Charging', 'Discharging']].sum(axis=1).cumsum()

dfplot[['Charging', 'Discharging']].plot.area(ax=ax[0])
dfplot[['Stored energy']].plot.area(ax=ax[1])
for iax, ylab in enumerate(['Power', 'Energy']): ax[iax].set_ylabel(ylab)<jupyter_output><empty_output><jupyter_text>Run disaggregation for all component profile assignment kindsHere we loop over all component profile assignment kinds, instantiate the StDisaggregator class and call the main run() method. The printed differences shown in the output serve to compare the total profiles with the disaggregated ones and should be zero.<jupyter_code>df_full_all = pd.DataFrame()
df_step_evts_all = pd.DataFrame()

for kind in ['share', 'leftright', 'rightleft', 'top', 'bottom']:

    tvd = std.StDisaggregator(df, 1, kind)
    tvd.run()

    df_full_all = pd.concat([df_full_all, tvd.df_full_stacked.assign(kind=kind)], axis=0)
    df_step_evts_all = pd.concat([df_step_evts_all, tvd.df_step_evts.assign(kind=kind)], axis=0)<jupyter_output>100%|██████████| 6/6 [00:00<00:00, 52.02it/s]<jupyter_text>Result dataframesThe result tables are: df_full_all (std.StDisaggregator.df_full)containing the profiles of the components (nevent). It is indexed by nevents (=components) and time slot. Each nevent corresponds to a well-defined iteration.* **echg/edch** are external charging and discharging, i.e. including losses; note that these are the original profiles, copied to each iteration* **ichg/idch** are the temporal derivatives of the storage energy contentTo obtain the external component charging and discharging profiles we need to adjust for the round trip losses.<jupyter_code>display(df_full_all.set_index(['kind', 'iteration', 'nevent', 't']).head(10))<jupyter_output><empty_output><jupyter_text>df_step_evts_all (std.StDisaggregator.df_step_evts)containing the aggregate properties of the components. This is indexed by the components (nevent). This table is generated for convenience. All properties can be generated from the df_full_all table.Main columns:* **ichg_final/idch_final**: Total component energy, not including losses.* **wgt_center_erg_ichg/wgt_center_erg_idch**: Energy weighted mean of slots for charging and discharging.* **time_diff_icd**: Time difference between charging and discharging calculated from the energy weighted means of the slots<jupyter_code>display(df_step_evts_all.set_index(['kind', 'iteration', 'nevent']).head(10))<jupyter_output><empty_output><jupyter_text>Illustration of disaggregation Plot of components for all profile assignments.<jupyter_code># Add efficiencies to compensate for round trip losses
dfplot = df_full_all.join(df_step_evts_all.set_index(['nevent', 'kind'])['eff'], on=['nevent', 'kind'])

dfplot = dfplot.assign(Charging=dfplot.ichg / dfplot.eff, Discharging=-dfplot.idch * dfplot.eff)
dfplot = dfplot.set_index(['kind', 'nevent', 't'])
dfplot['Stored energy'] = dfplot.groupby(level=['kind', 'nevent']).apply(lambda x: (x.Charging + x.Discharging).cumsum().reset_index([0, 1], drop=True))
dfplot = dfplot.sort_index()
dfplot.loc[dfplot['Stored energy'].abs() < 1e-4, 'Stored energy'] = 0

dfplot = dfplot.pivot_table(index=['kind', 't'], columns=['iteration', 'nevent'], values=['Charging', 'Discharging', 'Stored energy'])

fig, axarr = plt.subplots(5, 2, sharex=True)
fig.set_size_inches(10, 8)
map_ax = [(0, ['Charging', 'Discharging'], 'Power'),
          (1, ['Stored energy'], 'Energy')]

for ax_x, cols, ylabel in map_ax:
    for ax_y, kind in enumerate(dfplot.index.get_level_values('kind').unique()):

        ax = axarr[ax_y, ax_x]
        dfplot.loc[kind][cols].plot.area(linewidth=0, ax=ax, stacked=True, legend=False)
        ax.set_ylabel(ylabel)
        ax.set_title(kind)<jupyter_output><empty_output><jupyter_text>Plots by iteration for all profile assignments.<jupyter_code>dfplot = dfplot.sum(level=[0, 'iteration'], axis=1)
fig, axarr = plt.subplots(5, 2, sharex=True)
fig.set_size_inches(10, 8)
map_ax = [(0, ['Charging', 'Discharging'], 'Power'),
          (1, ['Stored energy'], 'Energy')]

for ax_x, cols, ylabel in map_ax:
    for ax_y, kind in enumerate(dfplot.index.get_level_values('kind').unique()):

        ax = axarr[ax_y, ax_x]
        dfplot.loc[kind][cols].plot.area(linewidth=0, ax=ax, stacked=True, legend=False)
        ax.set_ylabel(ylabel)
        ax.set_title(kind)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13190184049079753, 'fraction_numerical': 0.011311349693251534, 'mean_word_length': 6.410511363636363, 'pattern_counts': {'":': 0, '<': 30, '<?xml version=': 0, '>': 28, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '210711', 'license': 'MIT', 'n_tokens_mistral': 1824, 'n_tokens_neox': 1719, 'n_words': 447, 'path': 'storedisagg/example/std_example.ipynb', 'repo_name': 'mcsoini/storedisagg', 'size': '5216'}