Files
ACLC26/ACLC26_data_analysis_runfile.ipynb

36 KiB

Data Analysis for CWFT Experiments Runfile

Atmospheric Chemistry Lab Course 2026 Edition

Contact nicole.leemann@psi.ch if you encounter any bugs.

Import

In [ ]:
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from pathlib import Path
from babel.dates import format_date
from scipy.optimize import curve_fit
import re
import ast
In [ ]:
import ACLC26_data_analysis_module as dam

Initialize Experiment Class

In [ ]:
# Input

from_summary = False # set to true to load parameters from a summary file instead of manual input

savename = "20251202_FeCit_CA_Nitrate_1_10_10_RH80" # name that will be used for saving the processed data and data analysis summary of the experiment
#savename = "20251204_FeCit_CA_Nitrate_1_10_10_RH50"
#savename = "20251203_CA_Nitrate_1_1_RH80"
savefolder = Path("C:/Users/leeman_n/OneDrive - Paul Scherrer Institut/Documents/Teaching/2026_AtmosChemLabCourse/ACLC26_DataAnalysis/ACLC26_Output") / savename # name of the folder to save the outputs and summary of the experiment

color_dict = { # colors that will be used for plotting
            "T360U_CO2": ["#807F80"], # CO2
            "T200_NOx": ["#F08080", "#84B8E9", "#C5A1F5"], # NO, NO2, NOx 
            "Ecophysics_NOx_899": ["#CC2626","#0B60AF", "#5533B4"], # NO, NO2, NOx
            "ICAD_HONO": ["#db2fa8", "#7ddfc9"], # HONO, NO2
            "ICAD_NO2": ["#6cdf72"] # NO2
        }

if from_summary:
    FeCit_N = dam.init_from_summary(savefolder, savename, color_dict) # initialize from summary

else: # manual input
    exp_name = "FeCit/CA/Nitrate 1:10:10 RH 80% (2.12.2026)" # for plot title (or comparisons)

    #instrument_list = ["T360U_CO2", "T200_NOx", "Ecophysics_NOx_899", "ICAD_HONO", "ICAD_NO2", "Humidity_Sensors"]
    instrument_list = ["T360U_CO2", "Ecophysics_NOx_899", "ICAD_HONO", "ICAD_NO2", "Humidity_Sensors"] # list of all instruments used
    
    setup_dict = {
                "p_room": 979, # ambient pressure [mbar]
                "T_room": 23,  # room temperature [°C]
                "T_tube": 18,  # CWFT temperature [°C]
                "T_hum": 15,   # humidifier temperature [°C]
                "flow_N2": 1300,  # MFC set flow for N2
                "flow_O2": 300,   # MFC set flow for O2
                "dilution": 3000, # MFC set flow for N2 dilution
                "dil_BTOF": 1000, # MFC set flow for BTOF dilution
                "tube_length": 32.6, # tube length [cm]
                "tube_diam": 1.64,   # tube inner diameter [cm]
                "vol_coat": 1000     # volume of coating solution [muL]
            }
    exp_start =         "2025-12-02 09:00:00"   # YYYY-MM-DD hh:mm:ss    (for locating the correct data)     
    time_tube_in =      "2025-12-02 14:58:19"   # switch from bypass to tube flow  
    time_lights_on =    "2025-12-02 15:18:25"   # irradiation period
    time_lights_off =   "2025-12-02 15:53:37"  
    time_lights_on_2 =  None # YYYY-MM-DD hh:mm:ss or None # second irradiation period (optional)
    time_lights_off_2 = None
    time_tube_out =     "2025-12-02 16:09:08"  # switch from tube to bypass flow 
    exp_end =           "2025-12-02 18:00:00"  # for start and end: choose big enough interval that also includes the timeshifted data  

    FeCit_N = dam.experiment(exp_name, instrument_list, setup_dict, exp_start, exp_end, time_tube_in, time_lights_on, time_lights_off, time_tube_out, color_dict, savefolder,
                         time_lights_on_2, time_lights_off_2) # initialize
In [ ]:
# set up folders for saving processed data and plots
savefolder.mkdir(parents=True, exist_ok=True) # create folder if it doesn't exist already (parent folder)

savefolder_data = Path(savefolder) / "Processed_Data" # csv files of processed data for each instrument
savefolder_data.mkdir(parents=True, exist_ok=True)
savefolder_plots = Path(savefolder) / "Plots" # all saved plots
savefolder_plots.mkdir(parents=True, exist_ok=True)

Load raw data

In [ ]:
if "T360U_CO2" in FeCit_N.instruments:
    FeCit_N.get_rawdata_T360U_CO2() 
    display(FeCit_N.rawdata_co2.head())
    print("Size: ", FeCit_N.rawdata_co2.shape)
In [ ]:
# if "T200_NOx" in FeCit_N.instruments:
#     FeCit_N.get_rawdata_T200_NOx()
#     display(FeCit_N.rawdata_nox.head())
#     print("Size: ", FeCit_N.rawdata_nox.shape)
In [ ]:
if "Humidity_Sensors" in FeCit_N.instruments:
    FeCit_N.get_rawdata_RH()
    display(FeCit_N.rawdata_rh.head())
    print("Size: ", FeCit_N.rawdata_rh.shape)
In [ ]:
if "ICAD_HONO" in FeCit_N.instruments:
    FeCit_N.get_rawdata_ICAD_HONO()
    display(FeCit_N.rawdata_icad_hono.head())
    print("Size: ", FeCit_N.rawdata_icad_hono.shape)
In [ ]:
if "ICAD_NO2" in FeCit_N.instruments:
    FeCit_N.get_rawdata_ICAD_NO2()
    display(FeCit_N.rawdata_icad_no2.head())
    print("Size: ", FeCit_N.rawdata_icad_no2.shape)
In [ ]:
if "Ecophysics_NOx_899" in FeCit_N.instruments:
    FeCit_N.get_rawdata_NOx_eco()
    display(FeCit_N.rawdata_nox_eco.head())
    print("Size: ", FeCit_N.rawdata_nox_eco.shape)

Preview Plot

For checking and adjusting alignment between instruments

In [ ]:
# copy the output to the cell below for easier adjustment ;)
# set pd.Timestamp("None") to None!

print(f"FeCit_N.time_tube_in =      pd.Timestamp(\"{FeCit_N.time_tube_in}\")")
print(f"FeCit_N.time_lights_on =    pd.Timestamp(\"{FeCit_N.time_lights_on}\")")
print(f"FeCit_N.time_lights_off =   pd.Timestamp(\"{FeCit_N.time_lights_off}\")")
print(f"FeCit_N.time_lights_on_2 =  pd.Timestamp(\"{FeCit_N.time_lights_on_2}\")")
print(f"FeCit_N.time_lights_off_2 = pd.Timestamp(\"{FeCit_N.time_lights_off_2}\")")
print(f"FeCit_N.time_tube_out =     pd.Timestamp(\"{FeCit_N.time_tube_out}\")")
In [ ]:
# from_summary = False # uncomment this to make adjustments to the parameters from the summary

if from_summary==False: # only overwrite if not loading from summary

    # set timeshift of instruments (align times to CO2 analyzer)
    FeCit_N.timeshift_nox = pd.Timedelta(minutes=0, seconds=0)
    FeCit_N.timeshift_nox_eco = pd.Timedelta(minutes=56, seconds=34)
    FeCit_N.timeshift_icad_hono = pd.Timedelta(minutes=61, seconds=50)
    FeCit_N.timeshift_icad_no2 = pd.Timedelta(minutes=60, seconds=0)
    FeCit_N.timeshift_rh = pd.Timedelta(minutes=0, seconds=0)

    # for demo
    # FeCit_N.timeshift_nox_eco = pd.Timedelta(minutes=60, seconds=0)
    # FeCit_N.timeshift_icad_hono = pd.Timedelta(minutes=60, seconds=0)
    # FeCit_N.timeshift_icad_no2 = pd.Timedelta(minutes=60, seconds=0)

    # set irradiation time periods & tube in / out times
    FeCit_N.time_tube_in =      pd.Timestamp("2025-12-02 14:58:19")
    FeCit_N.time_lights_on =    pd.Timestamp("2025-12-02 15:18:25")
    FeCit_N.time_lights_off =   pd.Timestamp("2025-12-02 15:53:37")
    FeCit_N.time_lights_on_2 =  None
    FeCit_N.time_lights_off_2 = None
    FeCit_N.time_tube_out =     pd.Timestamp("2025-12-02 16:09:08")

    # FeCit_N.time_tube_in =      pd.Timestamp("2025-12-04 11:47:25")
    # FeCit_N.time_lights_on =    pd.Timestamp("2025-12-04 12:00:27")
    # FeCit_N.time_lights_off =   pd.Timestamp("2025-12-04 12:35:41")
    # FeCit_N.time_lights_on_2 =  None
    # FeCit_N.time_lights_off_2 = None
    # FeCit_N.time_tube_out =     pd.Timestamp("2025-12-04 13:35:26")

    
In [ ]:
preview = FeCit_N.preview_plot(FeCit_N.instruments) # clean up data and make preview plot
#preview.update_xaxes(range=(pd.Timestamp("2025-12-04 10:30:00"), pd.Timestamp("2025-12-04 14:30:00"))) # set x-axis range manually!
preview.update_yaxes(range=(0,5), secondary_y=False)  # y-axis range (left)
preview.show()
#preview.write_html(f"{savefolder_plots}/{savename}_Preview_Plot.html")
In [ ]:
if "Humidity_Sensors" in FeCit_N.instruments:
    rhtdp_plot = FeCit_N.RHTDP_plot()
    #rhtdp_plot.update_xaxes(range=(pd.Timestamp("2025-11-07 13:00:00"), pd.Timestamp("2025-11-07 17:00:00")))
    #rhtdp_plot.update_yaxes(title_text="RH [%]", range=[10, 35], row=1, col=1)
    
    #rhtdp_plot.show() # uncomment to show the plot

Background Fitting

In [ ]:
# add column with numeric timeaxis to all datasets

if "T360U_CO2" in FeCit_N.instruments:
    FeCit_N.get_num_timeaxis(FeCit_N.predata_co2, ts=pd.Timedelta(minutes=0, seconds=0))
if "T200_NOx" in FeCit_N.instruments:
    FeCit_N.get_num_timeaxis(FeCit_N.predata_nox, ts=FeCit_N.timeshift_nox)
if "Ecophysics_NOx_899" in FeCit_N.instruments:
    FeCit_N.get_num_timeaxis(FeCit_N.predata_nox_eco, ts=FeCit_N.timeshift_nox_eco)
if "ICAD_HONO" in FeCit_N.instruments:
    FeCit_N.get_num_timeaxis(FeCit_N.predata_icad_hono, ts=FeCit_N.timeshift_icad_hono, ts_sign="+")
if "ICAD_NO2" in FeCit_N.instruments:
    FeCit_N.get_num_timeaxis(FeCit_N.predata_icad_no2, ts=FeCit_N.timeshift_icad_no2, ts_sign="+")
if "Humidity_Sensors" in FeCit_N.instruments:
    FeCit_N.get_num_timeaxis(FeCit_N.predata_rh, ts=FeCit_N.timeshift_rh)
FeCit_N.predata_icad_hono.head() # preview
FeCit_N.predata_co2.head()
In [ ]:
# CO2
if "T360U_CO2" in FeCit_N.instruments:

    df_data = FeCit_N.predata_co2.copy()
    if from_summary==False:
        FeCit_N.bkgint_co2 = ['2025-12-02 12:15:00', '2025-12-02 13:30:00'] # manual input interval for bkg fit

    pred_start, pred_end = pd.Timestamp(FeCit_N.bkgint_co2[0]), pd.Timestamp(FeCit_N.bkgint_co2[1]) # if load from summary

    # fit the background with linear function and return plot & fit parameters
    bkgfit_plot, popt, pcov, residuals = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw CO2 [ppm]", mode="lin")
    bkgfit_plot.update_layout(height=500, width=1500) # figure size
    bkgfit_plot.update_yaxes(range=(2,4))
    #FeCit_N.add_timemarkers(bkgfit_plot)  # add yellow rectangle to mark irradiation time
    bkgfit_plot.show()
    #bkgfit_plot.write_html(f"{savefolder_plots}/{savename}_Background_T360U_CO2.html") # save interactive plot
    bkgfit_plot.write_image(f"{savefolder_plots}/{savename}_Background_T360U_CO2.png")  # save static plot

    FeCit_N.bkgcorr_co2 = [popt] # save the parameters of the background fit

    print(popt)

    # residuals_plot = px.scatter(x=df_data["Time since Irradiation [min]"],y=residuals, title="residuals")
    # residuals_plot.update_layout(height=500, width=1500)
    # residuals_plot.show()

    # y_true = df_data["raw CO2 [ppm]"].to_numpy()
    # ss_res = (residuals**2).sum()
    # ss_tot = ((y_true - y_true.mean())**2).sum()

    # r2 = 1 - ss_res / ss_tot
    # print(r2)
In [ ]:
# NOx eco
if "Ecophysics_NOx_899" in FeCit_N.instruments:

    df_data = FeCit_N.predata_nox_eco.copy()
    if from_summary==False:
        FeCit_N.bkgint_nox_eco = ['2025-12-02 13:00:00', '2025-12-02 15:15:00'] # manual input
        #FeCit_N.bkgint_nox_eco = ['2025-12-04 10:40:00', '2025-12-04 12:00:00']

    pred_start, pred_end = pd.Timestamp(FeCit_N.bkgint_nox_eco[0]), pd.Timestamp(FeCit_N.bkgint_nox_eco[1]) # if load from summary

    # individual plots
    bkgfit_plot_poly, popt_poly, pcov_poly, resi_poly = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw NO2 [ppb]", mode="poly")
    #bkgfit_plot_poly.show()
    #bkgfit_plot_logdec, popt_logdec, pcov_logdec, resi_logdec = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw NO2 [ppb]", mode="log_decay")
    #bkgfit_plot_logdec.show()
    bkgfit_plot1, popt1, pcov1, resi1 = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw NO [ppb]", mode="lin")
    #bkgfit_plot1.show()
    bkgfit_plot2, popt2, pcov2, resi2 = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw NO2 [ppb]", mode="lin")
    #bkgfit_plot2.show()
    bkgfit_plot3, popt3, pcov3, resi3 = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw NOx [ppb]", mode="lin")
    #bkgfit_plot3.show()

    # combined plot
    fig = make_subplots(
        rows=2,
        cols=2,
        subplot_titles=["NO", "NO2 lin", "NOx", "NO2 log decay"],
        horizontal_spacing = 0.03,
        vertical_spacing = 0.1
    )

    for trace in bkgfit_plot1.data:
        fig.add_trace(trace, row=1, col=1)

    for trace in bkgfit_plot2.data:
        fig.add_trace(trace, row=1, col=2)

    for trace in bkgfit_plot3.data:
        fig.add_trace(trace, row=2, col=1)

    #for trace in bkgfit_plot_logdec.data:
    #    fig.add_trace(trace, row=2, col=2)

    fig.update_layout(
        showlegend=True,
        title_text="Ecophysics NOx 899"
    )
    fig.update_layout(height=750, width=1500)
    #FeCit_N.add_timemarkers(fig)
    fig.show()
    #fig.write_html(f"{savefolder_plots}/{savename}_Background_Ecophysics_NOx_899.html")
    fig.write_image(f"{savefolder_plots}/{savename}_Background_Ecophysics_NOx_899.png") # save static plot

    FeCit_N.bkgcorr_nox_eco = [popt1, popt2, popt3] # save fit parameters
    print(FeCit_N.bkgcorr_nox_eco)
In [ ]:
# NOx
if "T200_NOx" in FeCit_N.instruments:

    df_data = FeCit_N.predata_nox.copy()
    if from_summary==False:
        FeCit_N.bkgint_nox = ['2025-12-02 13:00:00', '2025-12-02 15:15:00'] # manual input

    pred_start, pred_end = pd.Timestamp(FeCit_N.bkgint_nox[0]), pd.Timestamp(FeCit_N.bkgint_nox[1]) # if load from summary
  
    bkgfit_plot1, popt1, pcov1, resi1 = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw NO [ppb]", mode="lin")
    #bkgfit_plot1.show()
    bkgfit_plot2, popt2, pcov2, resi2 = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw NO2 [ppb]", mode="lin")
    #bkgfit_plot2.show()
    bkgfit_plot3, popt3, pcov3, resi3 = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw NOx [ppb]", mode="lin")
    #bkgfit_plot3.show()

    fig = make_subplots(
        rows=2,
        cols=2,
        subplot_titles=["NO", "NO2", "NOx", " "],
        horizontal_spacing = 0.03,
        vertical_spacing = 0.1
    )

    for trace in bkgfit_plot1.data:
        fig.add_trace(trace, row=1, col=1)

    for trace in bkgfit_plot2.data:
        fig.add_trace(trace, row=1, col=2)

    for trace in bkgfit_plot3.data:
        fig.add_trace(trace, row=2, col=1)

    fig.update_layout(
        #height=800,
        showlegend=True,
        title_text="T200 NOx"
    )
    #FeCit_N.add_timemarkers(fig)
    fig.update_layout(height=750, width=1500)
    fig.show()
    #fig.write_html(f"{savefolder_plots}/{savename}_Background_T200_NOx.html")
    fig.write_image(f"{savefolder_plots}/{savename}_Background_T200_NOx.png") # save static plot

    FeCit_N.bkgcorr_nox = [popt1, popt2, popt3] # save fit parameters
    print(FeCit_N.bkgcorr_nox)
In [ ]:
# ICAD HONO
if "ICAD_HONO" in FeCit_N.instruments:

    df_data = FeCit_N.predata_icad_hono.copy()
    if from_summary==False:
        FeCit_N.bkgint_icad_hono = ['2025-12-02 11:00:00', '2025-12-02 15:00:00'] # manual input

    pred_start, pred_end = pd.Timestamp(FeCit_N.bkgint_icad_hono[0]), pd.Timestamp(FeCit_N.bkgint_icad_hono[1]) # if load from summary
        
    bkgfit_plot1, popt1, pcov1, resi1 = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw HONO [ppb]", mode="lin")
    #bkgfit_plot1.show()
    bkgfit_plot2, popt2, pcov2, resi2 = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw NO2 [ppb]", mode="lin")
    #bkgfit_plot2.show()

    fig = make_subplots(
        rows=1,
        cols=2,
        subplot_titles=["HONO", "NO2"],
        horizontal_spacing = 0.03,
        vertical_spacing = 0.1
    )

    for trace in bkgfit_plot1.data:
        fig.add_trace(trace, row=1, col=1)

    for trace in bkgfit_plot2.data:
        fig.add_trace(trace, row=1, col=2)

    #FeCit_N.add_timemarkers(fig)
    fig.update_layout(height=450, width=1500, title_text="ICAD HONO")
    fig.show()
    #fig.write_html(f"{savefolder_plots}/{savename}_Background_ICAD_HONO.html")
    fig.write_image(f"{savefolder_plots}/{savename}_Background_ICAD_HONO.png") # save static plot

    FeCit_N.bkgcorr_icad_hono = [popt1, popt2] # save fit parameters
    print(FeCit_N.bkgcorr_icad_hono)
In [ ]:
# ICAD NO2
if "ICAD_NO2" in FeCit_N.instruments:
 
    df_data = FeCit_N.predata_icad_no2.copy()
    if from_summary==False:
        FeCit_N.bkgint_icad_no2 = ['2025-12-02 12:00:00', '2025-12-02 15:00:00'] # manual input

    pred_start, pred_end = pd.Timestamp(FeCit_N.bkgint_icad_no2[0]), pd.Timestamp(FeCit_N.bkgint_icad_no2[1]) # if load from summary
        
    bkgfit_plot, popt, pcov, residuals = FeCit_N.get_bkg_fit(df_data, pred_start, pred_end, param="raw NO2 [ppb]", mode="lin")
    bkgfit_plot.update_layout(height=500, width=1500)
    #FeCit_N.add_timemarkers(bkgfit_plot)
    bkgfit_plot.show()
    #bkgfit_plot.write_html(f"{savefolder_plots}/{savename}_Background_ICAD_NO2.html")
    bkgfit_plot.write_image(f"{savefolder_plots}/{savename}_Background_ICAD_NO2.png") # save static plot

    FeCit_N.bkgcorr_icad_no2 = [popt] # save fit parameters
    print(FeCit_N.bkgcorr_icad_no2)

Final Processing

Finish processing the instrument data (correct for timeshifts, background and dilution) and save the cleaned dataframes in a csv file (one per instrument)

In [ ]:
FeCit_N.process_data(savefolder, savename)
FeCit_N.data_co2.head() # preview

Summary

In [ ]:
savefile = f"{savefolder}/{savename}_DA_Summary.txt" # save summary of data analysis in this file
FeCit_N.get_summary(savefile)

Plots

One Instrument per Figure

In [ ]:
# CO2
if "T360U_CO2" in FeCit_N.instruments:

    #data = pd.read_csv(f"{savefolder}/Processed_Data/{savename}_Data_T360U_CO2.csv", sep=';') # get data from file
    data = FeCit_N.data_co2.copy() # get data from experiment

    fig_co2 = FeCit_N.plot_one_instrument(instr="T360U_CO2", data=data, plot_raw = False)
    fig_co2.update_xaxes(range=(-30, 60))
    fig_co2.update_yaxes(range=(-2,35))
    #fig_co2.update_yaxes(range=(-0.05,0.5))
    fig_co2.update_layout(height=500, width=1500)
    fig_co2.update_layout( 
        margin=dict(l=70, r=210, t=70, b=70) # control margins to keep the plot area consistent between plots
    )
    fig_co2.show()
    #fig_co2.write_html(f"{savefolder_plots}/{savename}_Plot_T360U_CO2.html")
    fig_co2.write_image(f"{savefolder_plots}/{savename}_Plot_T360U_CO2.png") # save static plot
In [ ]:
# NOx
if "T200_NOx" in FeCit_N.instruments:

    #data = pd.read_csv(f"{savefolder}/Processed_Data/{savename}_Data_T200_NOx.csv", sep=';') # get data from file
    data = FeCit_N.data_nox.copy() # get data from experiment

    fig_nox = FeCit_N.plot_one_instrument(instr="T200_NOx", data=data, plot_raw = False)
    fig_nox.update_xaxes(range=(-30, 60))
    #fig_nox.update_yaxes(range=(-10,40))
    fig_nox.update_layout(height=500, width=1500)
    fig_nox.update_layout(
        margin=dict(l=70, r=210, t=70, b=70) # control margins to keep the plot area consistent between plots
    )
    fig_nox.show()
    #fig_nox.write_html(f"{savefolder_plots}/{savename}_Plot_T200_NOx.html")
    fig_nox.write_image(f"{savefolder_plots}/{savename}_Plot_T200_NOx.png") # save static plot
In [ ]:
# NOx eco
if "Ecophysics_NOx_899" in FeCit_N.instruments:

    #data = pd.read_csv(f"{savefolder}/Processed_Data/{savename}_Data_Ecophysics_NOx_899.csv", sep=';') # get data from file
    data = FeCit_N.data_nox_eco.copy() # get data from experiment

    fig_nox_eco = FeCit_N.plot_one_instrument(instr="Ecophysics_NOx_899", data=data, plot_raw = False)
    fig_nox_eco.update_xaxes(range=(-30, 60))
    #fig_nox_eco.update_yaxes(range=(-5,80))
    fig_nox_eco.update_layout(height=500, width=1500)
    fig_nox_eco.update_layout(
        margin=dict(l=70, r=210, t=70, b=70) # control margins to keep the plot area consistent between plots
    )
    fig_nox_eco.show()
    #fig_nox_eco.write_html(f"{savefolder_plots}/{savename}_Plot_Ecophysics_NOx_899.html")
    fig_nox_eco.write_image(f"{savefolder_plots}/{savename}_Plot_Ecophysics_NOx_899.png") # save static plot
In [ ]:
# ICAD HONO
if "ICAD_HONO" in FeCit_N.instruments:

    inst = "ICAD_HONO"
    #data = pd.read_csv(f"{savefolder}/Processed_Data/{savename}_Data_ICAD_HONO.csv", sep=';') # get data from file
    data = FeCit_N.data_icad_hono.copy() # get data from experiment

    fig_hono = FeCit_N.plot_one_instrument(instr="ICAD_HONO", data=data, plot_raw = False)
    fig_hono.update_xaxes(range=(-30, 60))
    #fig_hono.update_yaxes(range=(-2,5))
    fig_hono.update_layout(height=500, width=1500)
    fig_hono.update_layout(
        margin=dict(l=70, r=210, t=70, b=70) # control margins to keep the plot area consistent between plots
    )
    fig_hono.show()
    #fig_hono.write_html(f"{savefolder_plots}/{savename}_Plot_ICAD_HONO.html")
    fig_hono.write_image(f"{savefolder_plots}/{savename}_Plot_ICAD_HONO.png") # save static plot
In [ ]:
# ICAD NO2
if "ICAD_NO2" in FeCit_N.instruments:

    #data = pd.read_csv(f"{savefolder}/Processed_Data/{savename}_Data_ICAD_NO2.csv", sep=';') # get data from file
    data = FeCit_N.data_icad_no2.copy() # get data from experiment

    fig_no2 = FeCit_N.plot_one_instrument(instr="ICAD_NO2", data=None, plot_raw = False)
    fig_no2.update_xaxes(range=(-30, 60))
    fig_no2.update_yaxes(range=(-1,8))
    fig_no2.update_layout(height=500, width=1500)
    fig_no2.update_layout(
        margin=dict(l=70, r=210, t=70, b=70) # control margins to keep the plot area consistent between plots
    )
    fig_no2.show()
    #fig_no2.write_html(f"{savefolder_plots}/{savename}_Plot_ICAD_NO2.html")
    fig_no2.write_image(f"{savefolder_plots}/{savename}_Plot_ICAD_NO2.png") # save static plot
In [ ]:
if "Humidity_Sensors" in FeCit_N.instruments:

    rh_plot = FeCit_N.RHTDP_plot(num_timeaxis=True) # make RH-temperature-dewpoint plot
    FeCit_N.add_timemarkers(rh_plot) # add yellow rectangle to mark irradiation period
    rh_plot.update_layout(height=500, width=1500, showlegend=True)
    rh_plot.update_xaxes(range=(-30, 60))

    #rh_plot.update_yaxes(range=(20, 30), row=1)
    #rh_plot.update_yaxes(range=(-0, 10), row=3)
    rh_plot.update_yaxes(range=(10, 30), row=1)
    rh_plot.update_yaxes(range=(-5, 5), row=3)

    rh_plot.update_layout(
        margin=dict(l=70, r=210, t=70, b=70) # control margins to keep the plot area consistent between plots
    )

    # dummy legend for irradiation
    rh_plot.add_trace(
        go.Scatter(
            x=[None],
            y=[None],
            mode="markers",
            marker=dict(
                size=12,
                symbol="square",
                color="gold",
                opacity=0.3
            ),
            name="Irradiation",
            showlegend=True,
        ),
        row=1, col=1
    )
    rh_plot.show()
    #rh_plot.write_html(f"{savefolder_plots}/{savename}_Plot_Humidity_Sensors.html")
    rh_plot.write_image(f"{savefolder_plots}/{savename}_Plot_Humidity_Sensors.png") # save static plot

Multiple Instruments per Figure

Note: Do not plot CO2 here as it will use the wrong yaxis unit label. Use plot_multiple_2axes to plot CO2 with other traces (below).

In [ ]:
instrs = ["ICAD_HONO", "ICAD_NO2"] # instruments to plot
title = "ICADs"

if set(instrs).issubset(set(FeCit_N.instruments)): # if chosen instruments were all actually used

    fig_icad = FeCit_N.plot_multiple(title, instrs)
    fig_icad.update_xaxes(range=(-30, 60))
    fig_icad.update_yaxes(range=(-5,45))
    fig_icad.update_layout(height=500, width=1500)
    fig_icad.update_layout(
        margin=dict(l=70, r=210, t=70, b=70) # control margins to keep the plot area consistent between plots
    )
    fig_icad.show()
    #fig_icad.write_html(f"{savefolder_plots}/{savename}_Plot_ICADs.html")
    #fig_icad.write_image(f"{savefolder_plots}/{savename}_Plot_ICADs.png") # save static plot
else:
    print("One or more instruments in instrs are not in instrument_list. Skip plot.")

Multiple Instruments Figures with 2 y-axes:

In [ ]:
instrs1 = ["T360U_CO2"] # instruments to plot on left axis (never mix CO2 with other instruments because of different units)
instrs2 = FeCit_N.instruments[1:-1] # everything except the first (CO2)
title = "All parameters"

if set(instrs1).issubset(set(FeCit_N.instruments)) and set(instrs2).issubset(set(FeCit_N.instruments)):
    fig = FeCit_N.plot_multiple_2axes(title, instrs1, instrs2)
    fig.update_xaxes(range=(-30, 60))

    ymin, ymax = -5, 35 # y-axis range 
    #ymin, ymax = -0.5, 1
    fig.update_yaxes(range=(ymin,ymax), secondary_y=False)
    fig.update_yaxes(range=(ymin*10,ymax*10), secondary_y=True) # right axis scaled based on left axis

    fig.update_layout(height=600, width=1800)
    fig.update_layout(
        margin=dict(l=70, r=210, t=70, b=70) # control margins to keep the plot area consistent between plots
    )
    fig.show()
    fig.write_html(f"{savefolder_plots}/{savename}_Plot_All.html") # save interactive plot
    fig.write_image(f"{savefolder_plots}/{savename}_Plot_All.png") # save static plot
else:
    print("One or more instruments in instrs1 or instrs2 are not in instrument_list. Skip plot.")
In [ ]:
instrs1 = ["Ecophysics_NOx_899"] # instruments to plot on left axis
instrs2 = ["ICAD_HONO", "ICAD_NO2"] # instruments to plot on right axis
title = "NOx analyser vs ICADs"

if set(instrs1).issubset(set(FeCit_N.instruments)) and set(instrs2).issubset(set(FeCit_N.instruments)):
    fig = FeCit_N.plot_multiple_2axes(title, instrs1, instrs2)
    fig.update_xaxes(range=(-30, 60))
    #fig.update_yaxes(range=(-10,40))
    fig.update_layout(height=500, width=1500)
    fig.update_layout(
        margin=dict(l=70, r=210, t=70, b=70) # control margins to keep the plot area consistent between plots
    )
    fig.show()
else:
    print("One or more instruments in instrs1 or instrs2 are not in instrument_list. Skip plot.")
In [ ]: