""" Lab 4 - Distribution modelling (normal distribution) for PV inverter data Reads the ORIGINAL sheet exported from Excel directly (no manual pre-cleaning), using the same reading technique fixed for Labs 1, 2 and 3. """ import pandas as pd import numpy as np import matplotlib.pyplot as plt from scipy.stats import norm FILE = "PV2013January.csv" def load_pv_sheet(path): """ Load one day's PV sheet exactly as Excel exports it: line 1: empty line 2: sensor names (TimeStamp / SENS0500... / WR11LV02...) line 3: the real column names (TimeStamp, ExlSolIrr, IntSolIrr, ...) line 4: units (hh:mm, W/m^2, W/m^2, ...) line 5+: data, with decimal numbers written as "11227,85" (comma instead of a dot, wrapped in quotes because a comma is otherwise the column separator). Returns a DataFrame with a proper TimeStamp column and every other column converted to a real number. """ df = pd.read_csv(path, skiprows=2) # ignore the 2 rows above the header df = df.drop(index=0).reset_index(drop=True) # drop the units row df = df.rename(columns={df.columns[0]: "TimeStamp"}) numeric_cols = df.columns.drop("TimeStamp") df[numeric_cols] = df[numeric_cols].apply( lambda col: pd.to_numeric(col.astype(str).str.replace(",", ".", regex=False), errors="coerce") ) return df df = load_pv_sheet(FILE) print("Loaded", len(df), "rows,", len(df.columns), "columns") print(df[["TimeStamp", "TmpMdul C", "Fac", "Pac"]].head()) def fit_and_report(name, data): """Compute mean and standard deviation, the parameters of the modelled normal distribution N(mean, std).""" mu = data.mean() sigma = data.std() print(f"{name}: n={len(data)} mean={mu:.3f} std={sigma:.3f}") return mu, sigma def plot_hist_with_normal(data, mu, sigma, xlabel, title, filename, color): """Plot the real data as a density histogram, with the modelled normal PDF -- built from the data's own mean and std -- drawn on top.""" fig, ax = plt.subplots(figsize=(6, 4.2)) ax.hist(data, bins=30, density=True, color=color, alpha=0.55, edgecolor="white", label="real data") if sigma > 0: xs = np.linspace(data.min(), data.max(), 300) ax.plot(xs, norm.pdf(xs, mu, sigma), color="black", linewidth=2, label=f"modelled N({mu:.2f}, {sigma:.2f})") ax.set_xlabel(xlabel) ax.set_ylabel("density") ax.set_title(title) ax.legend() fig.tight_layout() fig.savefig(filename, dpi=150) plt.close(fig) # ===================================================================== # Column F: TmpMdul C (module temperature) # ===================================================================== tmod = df["TmpMdul C"] print("\n--- F: TmpMdul C ---") mu_f, sigma_f = fit_and_report("TmpMdul C (raw)", tmod) plot_hist_with_normal(tmod, mu_f, sigma_f, "Module temperature, C", "F -- TmpMdul C: real data vs. modelled normal (raw)", "F_normal_raw.png", "#2F3C7E") # TmpMdul C has no physical zeros to filter (module temperature is never # exactly 0 C in this dataset), but we check anyway, per the general # "filter if needed" instruction established in Lab 3. tmod_nz = tmod[tmod != 0] if len(tmod_nz) != len(tmod): mu_f2, sigma_f2 = fit_and_report("TmpMdul C (zeros removed)", tmod_nz) plot_hist_with_normal(tmod_nz, mu_f2, sigma_f2, "Module temperature, C", "F -- TmpMdul C: real data vs. modelled normal (filtered)", "F_normal_filtered.png", "#2F3C7E") else: print("No zeros found in TmpMdul C -- filtered model is identical to the raw one.") # ===================================================================== # Column I: Fac (AC / grid frequency) -- dominated by zeros (inverter idle) # ===================================================================== fac = df["Fac"] print("\n--- I: Fac ---") mu_i, sigma_i = fit_and_report("Fac (raw, incl. zeros)", fac) plot_hist_with_normal(fac, mu_i, sigma_i, "Fac, Hz", "I -- Fac: real data vs. modelled normal (raw, incl. zeros)", "I_normal_raw.png", "#F96167") fac_nz = fac[fac > 0] mu_i2, sigma_i2 = fit_and_report("Fac (zeros removed)", fac_nz) plot_hist_with_normal(fac_nz, mu_i2, sigma_i2, "Fac, Hz", "I -- Fac: real data vs. modelled normal (zeros removed)", "I_normal_filtered.png", "#F96167") # ===================================================================== # Column P: Pac (AC power) -- also dominated by zeros (no generation at night) # ===================================================================== pac = df["Pac"] print("\n--- P: Pac ---") mu_p, sigma_p = fit_and_report("Pac (raw, incl. zeros)", pac) plot_hist_with_normal(pac, mu_p, sigma_p, "Pac, W", "P -- Pac: real data vs. modelled normal (raw, incl. zeros)", "P_normal_raw.png", "#2F3C7E") pac_nz = pac[pac > 0] mu_p2, sigma_p2 = fit_and_report("Pac (zeros removed)", pac_nz) plot_hist_with_normal(pac_nz, mu_p2, sigma_p2, "Pac, W", "P -- Pac: real data vs. modelled normal (zeros removed)", "P_normal_filtered.png", "#2F3C7E") print("\nAll figures saved.")