.. code:: ipython3 from seeq import spy import pandas as pd # Set the compatibility option so that you maximize the chance that SPy will remain compatible with your notebook/script spy.options.compatibility = 192 .. code:: ipython3 # Log into Seeq Server if you're not using Seeq Data Lab: spy.login(url='http://localhost:34216', credentials_file='../credentials.key', force=False) spy.swap ======== Performs “asset swapping” functionality equivalent to what you can achieve in Seeq Workbench by the “Swap in this asset…” button for an Asset found in the Data tab. When you create a calculated item (Signal, Condition, Scalar, Threshold Metric) that is based on stored signals that are part of an asset tree, Seeq allows you to “swap” the original asset that you used for a different asset that also has all of the constituent signals (named identically). You can then pull the data for the “swapped” calculation just like you would pull data for any other calculated item. Let’s walk through an example. Example ------- First we will query for some signals to use in our calculation and then push a calculation based on those signals. .. code:: ipython3 area_a_df = spy.search({ 'Path': 'Example >> Cooling Tower 1', 'Asset': 'Area A', 'Name': 'Temperature' }) pushed_items_df = spy.push(metadata=pd.DataFrame([{ 'Name': f'Added', 'Formula': '$t + 10', 'Formula Parameters': {'$t': area_a_df.iloc[0]} }, { 'Name': f'Hot', 'Formula': '$t > 80', 'Formula Parameters': {'$t': area_a_df.iloc[0]} }, { 'Name': f'Average on One Day in 2016', 'Formula': "$t.average(capsule('2016-12-18'))", 'Formula Parameters': {'$t': area_a_df.iloc[0]} }]), workbook='SPy Documentation Examples >> spy.swap') pushed_items_df Now we’ll query for the assets to swap in for the original Area A asset that we built the calculation for: .. code:: ipython3 areas_def_df = spy.search({ 'Path': 'Example >> Cooling Tower 2', 'Name': '/Area [DEF]/', 'Type': 'Asset' }, old_asset_format=False) areas_def_df Now all we have to do is execute the ``spy.swap()`` command to swap in these assets and get a bunch of new IDs we can use in ``spy.pull()``: .. code:: ipython3 swapped_df = spy.swap(pushed_items_df, areas_def_df, errors='catalog') You’ll notice a few things about the returned DataFrame: - The ``Original ID`` column corresponds to the calculated item for which we’re swapping assets. The values are repeated three times because we’re swapping three assets across three items (a signal, condition and scalar). That’s why there’s nine rows total. - Some of the rows don’t have an ``ID`` field because there were errors, which can be seen in the ``Result`` column. - The ``Swap Performed`` column includes precise details about what was swapped. The format is ``Swapped Out Asset --> Swapped In Asset``. In order to get a better look at the ``Result`` and ``Swap Performed`` columns, let’s remove some of the other columns: .. code:: ipython3 pd.set_option('display.max_colwidth', None) swapped_df[['ID', 'Name', 'Result', 'Swap Performed']] In Seeq’s Example data, Area F does not include a ``Temperature`` signal, so ``spy.swap()`` could not create a swapped calculation for that particular asset. For the swaps that succeeded, you can now pull the data for these swapped items like so: .. code:: ipython3 # First we'll drop the rows that had errors. successful_swaps_df = swapped_df[swapped_df['Result'] == 'Success'] spy.pull(successful_swaps_df, start='2021-01-01', end='2021-01-02') Multi-asset Swaps ----------------- Calculations can be based on more than one asset. That means swapping is more complex, because you must specify a pairing for each swappable asset. You accomplish this using “Swap Groups”. In the following example, we’ll push a calculation that is a summation of ``Compressor Power`` for Area A and Area B. Then we’ll swap out those assets for Areas D & E and then again for Areas D & F. .. code:: ipython3 areas_df = spy.search({ 'Path': 'Example >> Cooling Tower 1', 'Asset': '/Area [AB]/', 'Name': 'Compressor Power' }) added_together_df = spy.push(metadata=pd.DataFrame([{ 'Name': 'Added Together', 'Formula': '$a + $b', 'Formula Parameters': { '$a': areas_df[areas_df['Asset'] == 'Area A'], '$b': areas_df[areas_df['Asset'] == 'Area B'] } }]), workbook='SPy Documentation Examples >> spy.swap') added_together_df = spy.search({ 'Name': 'Added Together' }, include_swappable_assets=True, workbook='SPy Documentation Examples >> spy.swap') added_together_df Notice that we used the ``spy.search(include_swappable_assets=True)`` flag, which means that the ``added_together_df`` DataFrame includes a ``Swappable Assets`` column where each cell is an embedded DataFrame detailing the assets upon which the calculation is based: .. code:: ipython3 swappable_assets_df = added_together_df.iloc[0]['Swappable Assets'] swappable_assets_df That’s what we need in order to specify our “swap groups”. Now we have to create a DataFrame of assets with a ``Swap Group`` and ``Swap Out`` columns to achieve the pairing we talk about above: .. code:: ipython3 # Grab a DataFrame that contains Areas D, E and F cooling_tower_2_df = spy.search({ 'Path': 'Example >> Cooling Tower 2', 'Type': 'Asset' }, old_asset_format=False).sort_values(by='Name') # Create a DataFrame that includes the combination of areas: swap_groups_df = pd.DataFrame([ cooling_tower_2_df.iloc[0], # Area D cooling_tower_2_df.iloc[1], # Area E cooling_tower_2_df.iloc[0], # Area D cooling_tower_2_df.iloc[2]]) # Area F # Now we'll specify our "Swap Group" and "Swap Out" columns. The Swap Group column can be any signifier you wish to use # to group the swaps together. Any rows with the same value for the Swap Group will be grouped together for swapping # purposes. swap_groups_df['Swap Group'] = [1, 1, 2, 2] # The "Swap Out" column can be an asset ID string, the latter part of an asset's path, or a DataFrame row for an asset. # It must be for an asset that is part of the "Swappable Assets" found above. swap_groups_df['Swap Out'] = [ swappable_assets_df.iloc[0], # Area A swappable_assets_df.iloc[1], # Area B swappable_assets_df.iloc[0], # Area A swappable_assets_df.iloc[1], # Area B ] swap_groups_df Now we swap! Note that we’re going to have two new swapped calculations – one for each Swap Group. You can see that there are a couple of actions noted in the ``Swap Performed`` column (separated by a ``\n`` line break). .. code:: ipython3 swapped_df = spy.swap(added_together_df, swap_groups_df, old_asset_format=False) # Use a specific set of display properties so that the linebreaks are rendered correctly display(swapped_df[['ID', 'Name', 'Result', 'Swap Performed']].style.set_properties(**{ 'text-align': 'left', 'white-space': 'pre-wrap', })) We can pull just like we did before: .. code:: ipython3 spy.pull(swapped_df, start='2021-01-01', end='2021-01-02') Multi-level Swaps ----------------- If you have a hierarchy that contains “commonly named” nodes at the bottom of the tree, you’ll need to do a multi-level swap. Here’s an example where “Raw” and “Cleansed” are categorical child “assets” that are common between the uniquely-named assets (Area A & Area B): :: Multi-level Swap Example >> Area A >> Raw >> Temperature Cleansed >> Temperature Area B >> Raw >> Temperature Cleansed >> Temperature You must specify the uniquely-named assets as your “Swap Out” values, and SPy will figure out how to perform the swap at the lower level. Here’s an example where the categories are *Raw* and *Cleansed*: .. code:: ipython3 # First we have to create the asset tree we want from Example data. area_ab_df = spy.search({ 'Path': 'Example >> Cooling Tower 1', 'Asset': '/Area [AB]/', 'Name': 'Temperature' }, old_asset_format=False) raw_signals = area_ab_df.copy() raw_signals['Path'] = 'Multi-level Swap Example >> ' + raw_signals['Asset'] raw_signals['Asset'] = 'Raw' raw_signals['Reference'] = True cleansed_signals = area_ab_df.copy() cleansed_signals['Path'] = 'Multi-level Swap Example >> ' + cleansed_signals['Asset'] cleansed_signals['Asset'] = 'Cleansed' cleansed_signals['Reference'] = True combined_signal = pd.DataFrame([{ 'Name': 'Raw and Cleansed Averaged', 'Type': 'Signal', 'Formula': '($r + $c) / 2', 'Formula Parameters': { '$r': {'Path': 'Multi-level Swap Example >> Area A', 'Asset': 'Raw', 'Name': 'Temperature'}, '$c': {'Path': 'Multi-level Swap Example >> Area A', 'Asset': 'Cleansed', 'Name': 'Temperature'} } }]) pushed_items_df = spy.push(metadata=pd.concat([raw_signals, cleansed_signals, combined_signal], ignore_index=True), workbook='SPy Documentation Examples >> spy.swap') pushed_items_df[['ID', 'Type', 'Path', 'Asset', 'Name']] .. code:: ipython3 raw_plus_cleansed = pushed_items_df[pushed_items_df['Name'] == 'Raw and Cleansed Averaged'] area_a_df = pushed_items_df[(pushed_items_df['Type'] == 'Asset') & (pushed_items_df['Name'] == 'Area A')] area_b_df = pushed_items_df[(pushed_items_df['Type'] == 'Asset') & (pushed_items_df['Name'] == 'Area B')] # Create a Swap In / Swap Out pairing that uses the unique level of the tree, namely: # Swap Out: test_multilevel_swap >> Example >> Cooling Tower 1 >> Area A # Swap In: test_multilevel_swap >> Example >> Cooling Tower 1 >> Area B assets_df = area_b_df.copy() # Swap In assets_df['Swap Out'] = [area_a_df.iloc[0]] # Swap Out swap_results = spy.swap(raw_plus_cleansed, assets_df) display(swap_results[['ID', 'Name', 'Swap Performed']].style.set_properties(**{ 'text-align': 'left', 'white-space': 'pre-wrap', })) Detailed Help ------------- All SPy functions have detailed documentation to help you use them. Just execute ``help(spy.)`` like you see below. .. code:: ipython3 help(spy.swap)