generate_charts_month_with_daily_min_max.py 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. #!/usr/bin/env python3
  2. import os
  3. import sys
  4. from datetime import datetime
  5. import calendar
  6. import time
  7. import numpy as np
  8. import matplotlib.pyplot as plt
  9. from matplotlib.colors import LinearSegmentedColormap
  10. from matplotlib.patches import Polygon
  11. import pandas as pd
  12. marker = 'o'
  13. markersize = 4
  14. linestyle = '-'
  15. linewidth = 2
  16. colors = {
  17. 'max': 'red',
  18. 'min': 'blue',
  19. 'fill': 'gray'
  20. }
  21. def read_file_values(path):
  22. temp = pressure = humidity = None
  23. temp_u = pressure_u = humidity_u = ''
  24. try:
  25. with open(path, 'r') as f:
  26. for line in f:
  27. if 'Temperature' in line:
  28. try:
  29. parts = line.split(':', 1)[1].strip().split()
  30. temp = float(parts[0])
  31. temp_u = ' '.join(parts[1:]) if len(parts) > 1 else ''
  32. except:
  33. pass
  34. elif 'Pressure' in line:
  35. try:
  36. parts = line.split(':', 1)[1].strip().split()
  37. pressure = float(parts[0])
  38. pressure_u = ' '.join(parts[1:]) if len(parts) > 1 else ''
  39. except:
  40. pass
  41. elif 'Humidity' in line:
  42. try:
  43. parts = line.split(':', 1)[1].strip().split()
  44. humidity = float(parts[0])
  45. humidity_u = ' '.join(parts[1:]) if len(parts) > 1 else ''
  46. except:
  47. pass
  48. except FileNotFoundError:
  49. return (None, '', None, '', None, '')
  50. return (temp, temp_u, pressure, pressure_u, humidity, humidity_u)
  51. def read_measurements(data_folder):
  52. records = []
  53. year_folder = str(datetime.now().year)
  54. for root, dirs, files in os.walk(data_folder):
  55. for filename in files:
  56. if not filename.endswith('.txt'):
  57. continue
  58. if filename == year_folder:
  59. continue
  60. filepath = os.path.join(root, filename)
  61. try:
  62. timestamp = int(filename[:-4])
  63. except ValueError:
  64. print(f"Filename not a timestamp, skipping: {filename} (in {root})")
  65. continue
  66. file_time = datetime.fromtimestamp(timestamp)
  67. temp, temp_u, pressure, pressure_u, humidity, humidity_u = read_file_values(filepath)
  68. # fallback to year subfolder file if some values missing
  69. if (temp is None or pressure is None or humidity is None):
  70. alt_path = os.path.join(data_folder, year_folder, filename)
  71. if os.path.isfile(alt_path):
  72. at, at_u, ap, ap_u, ah, ah_u = read_file_values(alt_path)
  73. if temp is None and at is not None:
  74. temp, temp_u = at, at_u
  75. if pressure is None and ap is not None:
  76. pressure, pressure_u = ap, ap_u
  77. if humidity is None and ah is not None:
  78. humidity, humidity_u = ah, ah_u
  79. if temp is not None and pressure is not None and humidity is not None:
  80. records.append({
  81. 'timestamp': file_time,
  82. 'date': file_time.date(),
  83. 'day': file_time.day,
  84. 'temperature': temp,
  85. 'temperature_unit': temp_u,
  86. 'pressure': pressure,
  87. 'pressure_unit': pressure_u,
  88. 'humidity': humidity,
  89. 'humidity_unit': humidity_u
  90. })
  91. else:
  92. print(f"Missing data in file: {filename} (in {root}). Skipping.")
  93. return pd.DataFrame(records)
  94. def filter_month(df, month, year):
  95. if df.empty:
  96. return df
  97. return df[(df['timestamp'].dt.month == month) & (df['timestamp'].dt.year == year)]
  98. def daily_min_max(df, col):
  99. g = df.groupby('date')[col].agg(['min', 'max']).reset_index()
  100. g['day'] = pd.to_datetime(g['date']).dt.day
  101. g = g.sort_values('day')
  102. return g
  103. def plot_min_max(daily_df, col, out_folder, year, month, unit=''):
  104. if daily_df.empty:
  105. print(f"No data to plot for {col}")
  106. return
  107. last_day = calendar.monthrange(year, month)[1]
  108. fig, ax = plt.subplots(figsize=(12, 6))
  109. ax.plot(daily_df['day'], daily_df['min'],
  110. marker=marker, markersize=markersize,
  111. linestyle=linestyle, linewidth=linewidth,
  112. label='Min', color=colors['min'], zorder=3)
  113. ax.plot(daily_df['day'], daily_df['max'],
  114. marker=marker, markersize=markersize,
  115. linestyle=linestyle, linewidth=linewidth,
  116. label='Max', color=colors['max'], zorder=4)
  117. xs = list(daily_df['day'])
  118. ys_max = list(daily_df['max'])
  119. ys_min = list(daily_df['min'])
  120. if len(xs) >= 2:
  121. verts = [(x, y) for x, y in zip(xs, ys_max)] + [(x, y) for x, y in zip(xs[::-1], ys_min[::-1])]
  122. cmap = LinearSegmentedColormap.from_list('blue_red', [colors['min'], colors['max']])
  123. global_min = min(ys_min)
  124. global_max = max(ys_max)
  125. if global_min == global_max:
  126. global_min -= 0.5
  127. global_max += 0.5
  128. gradient = np.linspace(0, 1, 256).reshape(256, 1)
  129. gradient = np.repeat(gradient, 10, axis=1)
  130. im = ax.imshow(gradient, aspect='auto', cmap=cmap,
  131. extent=(0.5, last_day + 0.5, global_min, global_max),
  132. origin='lower', alpha=0.35, zorder=1)
  133. poly = Polygon(verts, closed=True, transform=ax.transData)
  134. im.set_clip_path(poly)
  135. ax.set_xlim(1, last_day)
  136. xticks = list(range(1, last_day + 1))
  137. xtick_labels = [f"{d}." for d in xticks]
  138. ax.set_xticks(xticks)
  139. ax.set_xticklabels(xtick_labels, rotation=0)
  140. ylabel = col.capitalize()
  141. if unit:
  142. ylabel += f' in {unit}'
  143. ax.set_ylabel(ylabel)
  144. ax.set_xlabel(f'Day of Month ({year}-{month:02d})')
  145. ax.set_title(f'{col.capitalize()} - Daily Min/Max Value for {year}-{month:02d}')
  146. ax.grid(True, alpha=0.25)
  147. ax.legend()
  148. fig.tight_layout()
  149. out_path = os.path.join(out_folder, f'{col}_daily_min_max.png')
  150. fig.savefig(out_path)
  151. plt.close(fig)
  152. print(f"Saved plot: {out_path}")
  153. def ensure_folder(path):
  154. if not os.path.exists(path):
  155. os.makedirs(path, exist_ok=True)
  156. def pick_unit(series_unit_col):
  157. if series_unit_col is None or series_unit_col.empty:
  158. return ''
  159. vals = series_unit_col.dropna().astype(str)
  160. vals = vals[vals != '']
  161. return vals.mode().iat[0] if not vals.empty else ''
  162. def main():
  163. if len(sys.argv) < 2:
  164. print("Usage: python3 script.py <month_number (1-12)>")
  165. sys.exit(1)
  166. try:
  167. month = int(sys.argv[1])
  168. assert 1 <= month <= 12
  169. except:
  170. print("Month must be an integer between 1 and 12.")
  171. sys.exit(1)
  172. data_folder = 'temperature'
  173. if not os.path.isdir(data_folder):
  174. print(f"Data folder not found: {data_folder}")
  175. sys.exit(1)
  176. start_time = time.time()
  177. df = read_measurements(data_folder)
  178. if df.empty:
  179. print("No valid measurements found.")
  180. sys.exit(1)
  181. df['timestamp'] = pd.to_datetime(df['timestamp'])
  182. year = datetime.now().year
  183. df_month = filter_month(df, month, year)
  184. if df_month.empty:
  185. print(f"No measurements for {month}/{year}.")
  186. sys.exit(0)
  187. # pick most common unit per measurement type for the month
  188. units = {
  189. 'temperature': pick_unit(df_month.get('temperature_unit')),
  190. 'pressure': pick_unit(df_month.get('pressure_unit')),
  191. 'humidity': pick_unit(df_month.get('humidity_unit'))
  192. }
  193. out_folder = f'plots_{year}_{month:02d}'
  194. ensure_folder(out_folder)
  195. for col in ['temperature', 'pressure', 'humidity']:
  196. daily = daily_min_max(df_month, col)
  197. plot_min_max(daily, col, out_folder, year, month, unit=units.get(col, ''))
  198. elapsed = time.time() - start_time
  199. print(f"Verarbeitung abgeschlossen in {elapsed:.2f}s. Plots in {out_folder}")
  200. if __name__ == '__main__':
  201. main()