1+ import pandas as pd
2+ import numpy as np
3+ import matplotlib .pyplot as plt
4+ from matplotlib .ticker import FuncFormatter
5+ import os
6+
7+ # --- 1. CONFIGURATION & STYLE ---
8+ COLOR_PALETTE = [
9+ "#0072B2" , "#E69F00" , "#009E73" , "#D55E00" , "#CC79A7" , "#F0E442" , "#8B4513" ,
10+ "#56B4E9" , "#F0A3FF" , "#FFB400" , "#00BFFF" , "#90EE90" , "#FF6347" , "#8A2BE2" ,
11+ "#CD5C5C" , "#4682B4" , "#FFDEAD" , "#32CD32" , "#D3D3D3" , "#999999"
12+ ]
13+ METRIC = "power_draw"
14+
15+
16+ def load_and_process_data ():
17+ """Loads parquet files and aligns timestamps."""
18+ print ("Loading data..." )
19+
20+ # Check if data exists (Warning only)
21+ if not os .path .exists ("../data/footprinter.parquet" ):
22+ print ("Warning: ../data/footprinter.parquet not found." )
23+
24+ # Load Data
25+ fp = pd .read_parquet ("../data/footprinter.parquet" ).groupby ("timestamp" )[METRIC ].sum ()
26+ odt = pd .read_parquet ("../data/opendt.parquet" ).groupby ("timestamp" )[METRIC ].sum ()
27+ rw = pd .read_parquet ("../data/real_world.parquet" ).groupby ("timestamp" )[METRIC ].sum ()
28+
29+ # --- Processing ---
30+ print ("Processing and aligning data..." )
31+
32+ def average_every_n (series , n ):
33+ return series .groupby (np .arange (len (series )) // n ).mean ()
34+
35+ # Average to 5-min intervals
36+ # OpenDT (2.5m) -> 2 samples = 5 min
37+ # Others (30s) -> 10 samples = 5 min
38+ odt = average_every_n (odt , 2 )
39+ fp = average_every_n (fp , 10 )
40+ rw = average_every_n (rw , 10 )
41+
42+ # Sync lengths (trim to shortest)
43+ min_len = min (len (odt ), len (fp ), len (rw ))
44+ odt = odt .iloc [:min_len ]
45+ fp = fp .iloc [:min_len ]
46+ rw = rw .iloc [:min_len ]
47+
48+ # Force Start Time to 2022-10-06 22:00:00
49+ start_time = pd .Timestamp ("2022-10-06 22:00:00" )
50+ # Fixed deprecation warning: used '5min' instead of '5T'
51+ timestamps = pd .date_range (start = start_time , periods = min_len , freq = "5min" )
52+
53+ # Apply clean timestamps
54+ odt .index = timestamps
55+ fp .index = timestamps
56+ rw .index = timestamps
57+
58+ return fp , odt , rw , timestamps , min_len
59+
60+
61+ def calculate_mape (ground_truth , simulation ):
62+ """Calculates Mean Absolute Percentage Error (MAPE)."""
63+ R = ground_truth .values
64+ S = simulation .values
65+ return np .mean (np .abs ((R - S ) / R )) * 100
66+
67+
68+ def generate_experiment_pdf (x , fp , odt , rw , timestamps , min_len ):
69+ """Generates the final publication plot."""
70+ print ("Generating final experiment PDF..." )
71+
72+ # Setup Figure (12, 5 size)
73+ plt .figure (figsize = (12 , 5 ))
74+ plt .grid (True )
75+
76+ # Plot Lines (Thick lines: lw=3)
77+ plt .plot (x , rw .values / 1000 , label = "Ground Truth" , color = COLOR_PALETTE [0 ], lw = 3 )
78+ plt .plot (x , fp .values / 1000 , label = "FootPrinter" , color = COLOR_PALETTE [1 ], lw = 3 )
79+ plt .plot (x , odt .values / 1000 , label = "OpenDT" , color = COLOR_PALETTE [2 ], lw = 3 )
80+
81+ ax = plt .gca ()
82+
83+ # --- Formatting X-Axis (Fixed Dates) ---
84+ target_dates = ["2022-10-08" , "2022-10-10" , "2022-10-12" , "2022-10-14" ]
85+ tick_dates = pd .to_datetime (target_dates )
86+
87+ tick_positions = []
88+ tick_labels = []
89+
90+ for d in tick_dates :
91+ seconds_diff = (d - timestamps [0 ]).total_seconds ()
92+ # 300 seconds = 5 minutes
93+ idx = int (seconds_diff / 300 )
94+
95+ tick_positions .append (idx )
96+ tick_labels .append (d .strftime ("%d/%m" ))
97+
98+ ax .set_xticks (tick_positions )
99+ # INCREASED FONT SIZE (was 14)
100+ ax .set_xticklabels (tick_labels , fontsize = 20 )
101+
102+ # Extend limit slightly to show last tick
103+ max_tick = max (tick_positions )
104+ if ax .get_xlim ()[1 ] < max_tick :
105+ ax .set_xlim (right = max_tick + (min_len * 0.02 ))
106+
107+ # --- Formatting Y-Axis ---
108+ y_formatter = FuncFormatter (lambda val , _ : f"{ int (val ):,} " )
109+ ax .yaxis .set_major_formatter (y_formatter )
110+ # INCREASED FONT SIZE (was 14)
111+ ax .tick_params (axis = 'y' , labelsize = 20 )
112+
113+ # Labels (INCREASED FONT SIZE)
114+ plt .ylabel ("Power Draw [kW]" , fontsize = 22 , labelpad = 10 )
115+ plt .xlabel ("Time [day/month]" , fontsize = 22 , labelpad = 10 )
116+ plt .ylim (bottom = 0 )
117+
118+ # Legend
119+ # FIXED: Cleaned 'loc' and ensured it matches bbox placement
120+ plt .legend (fontsize = 18 , loc = "upper center" , bbox_to_anchor = (0.5 , 1.22 ), ncol = 3 , framealpha = 1 )
121+
122+ plt .tight_layout ()
123+
124+ # Save
125+ plt .savefig ("exp1_plot_power_draw.pdf" , format = "pdf" , bbox_inches = "tight" )
126+ print ("Plot saved as 'exp1_plot_power_draw.pdf'" )
127+ # plt.show() # Uncomment if running in interactive mode/notebook
128+ plt .close ()
129+
130+
131+ def main ():
132+ # 1. Load Data
133+ fp , odt , rw , timestamps , min_len = load_and_process_data ()
134+ x = np .arange (min_len )
135+
136+ # 2. Calculate Stats
137+ mape_fp = calculate_mape (rw , fp )
138+ mape_odt = calculate_mape (rw , odt )
139+ print (f"Stats Calculated (Not plotted) - FP: { mape_fp :.2f} %, ODT: { mape_odt :.2f} %" )
140+
141+ # 3. Generate Plot
142+ generate_experiment_pdf (x , fp , odt , rw , timestamps , min_len )
143+ print ("Done!" )
144+
145+
146+ if __name__ == "__main__" :
147+ main ()
0 commit comments