dimwit 0.2.6__tar.gz → 0.2.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dimwit-0.2.6 → dimwit-0.2.8}/PKG-INFO +3 -1
- {dimwit-0.2.6 → dimwit-0.2.8}/dimwit/__init__.py +2 -1
- dimwit-0.2.8/dimwit/airflow.py +277 -0
- {dimwit-0.2.6 → dimwit-0.2.8}/dimwit/main.py +29 -2
- {dimwit-0.2.6 → dimwit-0.2.8}/pyproject.toml +3 -1
- {dimwit-0.2.6 → dimwit-0.2.8}/README.md +0 -0
- {dimwit-0.2.6 → dimwit-0.2.8}/dimwit/air_pollution.py +0 -0
- {dimwit-0.2.6 → dimwit-0.2.8}/dimwit/legacy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dimwit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.8
|
|
4
4
|
Summary: A package containing various functions and classes for fetching, transforming, and visualising data for personal metrics.
|
|
5
5
|
Home-page: https://github.com/danielsoutar/dimwit
|
|
6
6
|
Author: Daniel Soutar
|
|
@@ -13,6 +13,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
13
13
|
Requires-Dist: matplotlib (>=3.8.3,<4.0.0)
|
|
14
14
|
Requires-Dist: pandas (>=2.1.4,<3.0.0)
|
|
15
15
|
Requires-Dist: requests (>=2.31.0,<3.0.0)
|
|
16
|
+
Requires-Dist: scipy (>=1.12.0,<2.0.0)
|
|
17
|
+
Requires-Dist: seaborn (>=0.13.2,<0.14.0)
|
|
16
18
|
Project-URL: Repository, https://github.com/danielsoutar/dimwit
|
|
17
19
|
Description-Content-Type: text/markdown
|
|
18
20
|
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
from dimwit.main import get_moving_average_trend, populate_with_events
|
|
2
|
+
|
|
3
|
+
import datetime as dat
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import numpy as np
|
|
6
|
+
from scipy.stats import norm
|
|
7
|
+
import seaborn as sb
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def beginning_of_data():
|
|
11
|
+
uk_with_dst = dat.timezone(dat.timedelta(seconds=3600))
|
|
12
|
+
return dat.datetime(2023, 8, 21, 0, 0, tzinfo=uk_with_dst)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def get_events():
|
|
16
|
+
uk_with_dst = dat.timezone(dat.timedelta(seconds=3600))
|
|
17
|
+
dates = [
|
|
18
|
+
dat.datetime(2023, 9, 12, 13, 40, tzinfo=uk_with_dst),
|
|
19
|
+
dat.datetime(2024, 2, 4, 0, 0, tzinfo=dat.timezone.utc),
|
|
20
|
+
dat.datetime(2024, 2, 8, 0, 0, tzinfo=dat.timezone.utc),
|
|
21
|
+
]
|
|
22
|
+
descs = [
|
|
23
|
+
"Started using inhaler",
|
|
24
|
+
"Only using inhaler as needed",
|
|
25
|
+
"Using inhaler unless healthy",
|
|
26
|
+
]
|
|
27
|
+
return [(date, "gray", "--", desc) for date, desc in zip(dates, descs)]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def data_over_period(df, from_date, moving_average_window_size, events, title):
|
|
31
|
+
latest_df = df.loc[from_date:]
|
|
32
|
+
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
|
|
33
|
+
|
|
34
|
+
x = list(latest_df.index)
|
|
35
|
+
|
|
36
|
+
ax.scatter(x, latest_df["Recording 1"], label="Point 1", alpha=0.3)
|
|
37
|
+
ax.scatter(x, latest_df["Recording 2"], label="Point 2", alpha=0.3)
|
|
38
|
+
ax.scatter(x, latest_df["Recording 3"], label="Point 3", alpha=0.3)
|
|
39
|
+
|
|
40
|
+
# Plot a line for the maximum of the points
|
|
41
|
+
ax.plot(x, latest_df["Max Point"], label="Max", color="red")
|
|
42
|
+
|
|
43
|
+
window_size = moving_average_window_size
|
|
44
|
+
|
|
45
|
+
# Calculate the rolling average over the max values array
|
|
46
|
+
# TODO: Figure out whether to use actual data rather than repeating first
|
|
47
|
+
# point (k - 1) / 2 times, where possible.
|
|
48
|
+
rolling_average = get_moving_average_trend(
|
|
49
|
+
np.array(latest_df["Max Point"]), window_size
|
|
50
|
+
)
|
|
51
|
+
ax.plot(
|
|
52
|
+
x,
|
|
53
|
+
rolling_average,
|
|
54
|
+
label=f"Rolling Avg ({window_size})",
|
|
55
|
+
color="green",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
ax = populate_with_events(ax, events, x[0])
|
|
59
|
+
|
|
60
|
+
plt.legend()
|
|
61
|
+
plt.grid()
|
|
62
|
+
# Add a title to the entire figure
|
|
63
|
+
fig.suptitle(title, fontsize=16)
|
|
64
|
+
|
|
65
|
+
return fig, ax
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def differences_over_period(
|
|
69
|
+
df,
|
|
70
|
+
from_date,
|
|
71
|
+
moving_average_window_size,
|
|
72
|
+
events,
|
|
73
|
+
title,
|
|
74
|
+
):
|
|
75
|
+
latest_df = df.loc[from_date:]
|
|
76
|
+
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
|
|
77
|
+
|
|
78
|
+
max_vals = df[["Recording 1", "Recording 2", "Recording 3"]].max(axis=1)
|
|
79
|
+
min_vals = df[["Recording 1", "Recording 2", "Recording 3"]].min(axis=1)
|
|
80
|
+
df["Delta"] = max_vals - min_vals
|
|
81
|
+
|
|
82
|
+
x = list(latest_df.index)
|
|
83
|
+
|
|
84
|
+
ax.scatter(x, df["Delta"], label="Max-Min Difference", alpha=0.3)
|
|
85
|
+
|
|
86
|
+
window_size = moving_average_window_size
|
|
87
|
+
|
|
88
|
+
# Calculate the rolling average over the max values array
|
|
89
|
+
# TODO: Figure out whether to use actual data rather than repeating first
|
|
90
|
+
# point (k - 1) / 2 times, where possible.
|
|
91
|
+
rolling_average = get_moving_average_trend(
|
|
92
|
+
np.array(df["Delta"]),
|
|
93
|
+
window_size,
|
|
94
|
+
)
|
|
95
|
+
ax.plot(
|
|
96
|
+
x,
|
|
97
|
+
rolling_average,
|
|
98
|
+
label=f"Rolling Avg ({window_size})",
|
|
99
|
+
color="green",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
ax = populate_with_events(ax, events, x[0])
|
|
103
|
+
|
|
104
|
+
plt.legend()
|
|
105
|
+
plt.grid()
|
|
106
|
+
# Add a title to the entire figure
|
|
107
|
+
fig.suptitle(title, fontsize=16)
|
|
108
|
+
|
|
109
|
+
return fig, ax
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def get_pretty_image(
|
|
113
|
+
image_arr,
|
|
114
|
+
title,
|
|
115
|
+
colour_bar_title,
|
|
116
|
+
palette="viridis",
|
|
117
|
+
foreground_colour="white",
|
|
118
|
+
background_colour="black",
|
|
119
|
+
):
|
|
120
|
+
# 'flare_r' is a neat fire-y palette, as an alternative.
|
|
121
|
+
cmap = sb.color_palette(palette, as_cmap=True)
|
|
122
|
+
|
|
123
|
+
fig, ax = plt.subplots(figsize=(24, 4))
|
|
124
|
+
image = ax.imshow(
|
|
125
|
+
image_arr,
|
|
126
|
+
interpolation="nearest",
|
|
127
|
+
aspect="auto",
|
|
128
|
+
cmap=cmap,
|
|
129
|
+
)
|
|
130
|
+
colour_bar = plt.colorbar(image)
|
|
131
|
+
|
|
132
|
+
# set figure facecolor
|
|
133
|
+
ax.patch.set_facecolor(background_colour)
|
|
134
|
+
|
|
135
|
+
# set tick and ticklabel color
|
|
136
|
+
image.axes.get_xaxis().set_visible(False)
|
|
137
|
+
image.axes.get_yaxis().set_visible(False)
|
|
138
|
+
|
|
139
|
+
# set imshow outline
|
|
140
|
+
for spine in image.axes.spines.values():
|
|
141
|
+
spine.set_edgecolor(background_colour)
|
|
142
|
+
|
|
143
|
+
# set colorbar label plus label color
|
|
144
|
+
colour_bar.set_label(colour_bar_title, color=foreground_colour)
|
|
145
|
+
|
|
146
|
+
# set colorbar tick color
|
|
147
|
+
colour_bar.ax.yaxis.set_tick_params(color=foreground_colour)
|
|
148
|
+
|
|
149
|
+
# set colorbar edgecolor
|
|
150
|
+
colour_bar.outline.set_edgecolor(foreground_colour)
|
|
151
|
+
|
|
152
|
+
# set colorbar ticklabels
|
|
153
|
+
plt.setp(
|
|
154
|
+
plt.getp(colour_bar.ax.axes, "yticklabels"),
|
|
155
|
+
color=foreground_colour,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
_ = ax.set_title(title, color=foreground_colour)
|
|
159
|
+
fig.patch.set_facecolor(background_colour)
|
|
160
|
+
return fig, ax
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def generate_month_year_tuples(start=None, end=None):
|
|
164
|
+
if start is None:
|
|
165
|
+
start = (2023, 8)
|
|
166
|
+
|
|
167
|
+
if end is None:
|
|
168
|
+
end_date = dat.datetime.now()
|
|
169
|
+
end = (end_date.year, end_date.month)
|
|
170
|
+
else:
|
|
171
|
+
assert end[0] >= start[0]
|
|
172
|
+
if end[0] == start[0]:
|
|
173
|
+
assert end[1] > start[1]
|
|
174
|
+
|
|
175
|
+
month_names = {
|
|
176
|
+
1: "Jan",
|
|
177
|
+
2: "Feb",
|
|
178
|
+
3: "Mar",
|
|
179
|
+
4: "Apr",
|
|
180
|
+
5: "May",
|
|
181
|
+
6: "Jun",
|
|
182
|
+
7: "Jul",
|
|
183
|
+
8: "Aug",
|
|
184
|
+
9: "Sep",
|
|
185
|
+
10: "Oct",
|
|
186
|
+
11: "Nov",
|
|
187
|
+
12: "Dec",
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
tuples = []
|
|
191
|
+
current = start
|
|
192
|
+
|
|
193
|
+
while current != end:
|
|
194
|
+
current_year, current_month = current
|
|
195
|
+
tuples.append((current_year, current_month))
|
|
196
|
+
increment_year = current_month == 12
|
|
197
|
+
if increment_year:
|
|
198
|
+
next_year, next_month = current_year + 1, 1
|
|
199
|
+
else:
|
|
200
|
+
next_year, next_month = current_year, current_month + 1
|
|
201
|
+
|
|
202
|
+
current = (next_year, next_month)
|
|
203
|
+
|
|
204
|
+
tuples.append(end)
|
|
205
|
+
|
|
206
|
+
res = list(map(lambda t: (*t, f"{month_names[t[1]]} {str(t[0])}"), tuples))
|
|
207
|
+
|
|
208
|
+
return res
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def get_hist_data_for_month(df, year, month, use_maxes=False):
|
|
212
|
+
utc = dat.timezone.utc
|
|
213
|
+
|
|
214
|
+
dt = dat.datetime(year, month, 1, 0, 0, tzinfo=utc)
|
|
215
|
+
if month == 12:
|
|
216
|
+
end_dt = dat.datetime(year + 1, 1, 1, 0, 0, tzinfo=utc)
|
|
217
|
+
else:
|
|
218
|
+
end_dt = dat.datetime(year, month + 1, 1, 0, 0, tzinfo=utc)
|
|
219
|
+
|
|
220
|
+
month_data = df[["Recording 1", "Recording 2", "Recording 3"]][dt:end_dt]
|
|
221
|
+
month_data = np.array(month_data).reshape((-1, 3))
|
|
222
|
+
|
|
223
|
+
if use_maxes:
|
|
224
|
+
month_maxes = np.max(month_data, axis=1).reshape((-1, 1))
|
|
225
|
+
return month_maxes
|
|
226
|
+
|
|
227
|
+
return month_data
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def generate_overlaid_monthly_pdfs(df, xmin=500, xmax=800, num_samples=100):
|
|
231
|
+
fig, ax = plt.subplots(1, 1)
|
|
232
|
+
|
|
233
|
+
for year, month, name in generate_month_year_tuples():
|
|
234
|
+
all_samples_for_month = get_hist_data_for_month(df, year, month)
|
|
235
|
+
maxes_for_month = get_hist_data_for_month(
|
|
236
|
+
df,
|
|
237
|
+
year,
|
|
238
|
+
month,
|
|
239
|
+
use_maxes=True,
|
|
240
|
+
)
|
|
241
|
+
mean, std_dev = norm.fit(all_samples_for_month)
|
|
242
|
+
max_mean, _ = norm.fit(maxes_for_month)
|
|
243
|
+
|
|
244
|
+
pdf_x = np.linspace(xmin, xmax, num_samples)
|
|
245
|
+
pdf_y = norm.pdf(pdf_x, mean, std_dev)
|
|
246
|
+
|
|
247
|
+
month_label = f"{name} ({round(mean)}/{round(max_mean)})"
|
|
248
|
+
ax.plot(pdf_x, pdf_y, linewidth=2, label=month_label)
|
|
249
|
+
|
|
250
|
+
ax.legend()
|
|
251
|
+
ax.set_xlabel("Airflow (L/Min)")
|
|
252
|
+
ax.set_ylabel("Count")
|
|
253
|
+
ax.set_title("PDF of data, per month", size=16)
|
|
254
|
+
ax.set_xlim(xmin, xmax)
|
|
255
|
+
return fig, ax
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def create_monthly_airflow_histograms(df, month_year_tuples):
|
|
259
|
+
# Create a figure with subplots
|
|
260
|
+
rows = len(month_year_tuples)
|
|
261
|
+
fig, axes = plt.subplots(rows, 1, figsize=(5, 10), sharex=True)
|
|
262
|
+
|
|
263
|
+
# Plot data for each week on separate axes
|
|
264
|
+
for i, ax in enumerate(axes):
|
|
265
|
+
year, month, name = month_year_tuples[i]
|
|
266
|
+
month_data = get_hist_data_for_month(df, year, month)
|
|
267
|
+
flattened_month_data = month_data.ravel()
|
|
268
|
+
name = name + f" ({len(flattened_month_data)} samples)"
|
|
269
|
+
ax.hist(flattened_month_data, label=f"{name}")
|
|
270
|
+
ax.set_title(f"{name}")
|
|
271
|
+
ax.set_ylim(0, 50)
|
|
272
|
+
|
|
273
|
+
# Add a title to the entire figure
|
|
274
|
+
fig.suptitle("Distribution of data, per month", fontsize=16)
|
|
275
|
+
fig.supxlabel("Airflow (L/Min)")
|
|
276
|
+
fig.supylabel("Count")
|
|
277
|
+
return fig, axes
|
|
@@ -2,7 +2,7 @@ import datetime as dat
|
|
|
2
2
|
import dateutil
|
|
3
3
|
|
|
4
4
|
# Import matplotlib before pandas to avoid import ordering issues.
|
|
5
|
-
import matplotlib.pyplot as plt
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
6
|
import pandas as pd
|
|
7
7
|
import requests
|
|
8
8
|
import numpy as np
|
|
@@ -340,9 +340,12 @@ def get_aggregated_event_counts(ts, events, period):
|
|
|
340
340
|
df = df.pivot_table(
|
|
341
341
|
index="date", columns="categories", values="value", aggfunc="sum"
|
|
342
342
|
)
|
|
343
|
-
if period not in ["weekly", "daily"]:
|
|
343
|
+
if period.lower() not in ["monthly", "weekly", "daily"]:
|
|
344
344
|
raise Exception("Period argument not recognised")
|
|
345
345
|
|
|
346
|
+
if period == "monthly":
|
|
347
|
+
grouped_df = df.groupby([pd.Grouper(freq="ME")]).sum()
|
|
348
|
+
|
|
346
349
|
if period == "weekly":
|
|
347
350
|
df.index = df.index - pd.to_timedelta(7, unit="d")
|
|
348
351
|
grouped_df = df.groupby([pd.Grouper(freq="W")]).sum()
|
|
@@ -353,6 +356,9 @@ def get_aggregated_event_counts(ts, events, period):
|
|
|
353
356
|
return grouped_df
|
|
354
357
|
|
|
355
358
|
|
|
359
|
+
# TODO: Add 'get all events where equal to' function. Do not want a dense df.
|
|
360
|
+
|
|
361
|
+
|
|
356
362
|
def populate_with_events(ax, events, from_date):
|
|
357
363
|
for event in events:
|
|
358
364
|
event_date, event_colour, event_style, event_label = event
|
|
@@ -366,3 +372,24 @@ def populate_with_events(ax, events, from_date):
|
|
|
366
372
|
label=event_label,
|
|
367
373
|
)
|
|
368
374
|
return ax
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def plot_category(
|
|
378
|
+
title, df, categories, window_size, round_size, round_unit, from_date=None
|
|
379
|
+
):
|
|
380
|
+
fig, ax = plt.subplots(1, 1, figsize=(12, 6))
|
|
381
|
+
ax.set_title(title)
|
|
382
|
+
for category in categories:
|
|
383
|
+
if from_date is not None:
|
|
384
|
+
ax = df[from_date:].plot(y=category, ax=ax)
|
|
385
|
+
else:
|
|
386
|
+
ax = df.plot(y=category, ax=ax)
|
|
387
|
+
ax.set_xlabel("Time")
|
|
388
|
+
ax.set_ylabel("Frequency")
|
|
389
|
+
ax.legend(
|
|
390
|
+
[
|
|
391
|
+
f"{category} ({str(round_size) + round_unit}, k={window_size})"
|
|
392
|
+
for category in categories
|
|
393
|
+
],
|
|
394
|
+
)
|
|
395
|
+
return fig, ax
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "dimwit"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.8"
|
|
4
4
|
description = "A package containing various functions and classes for fetching, transforming, and visualising data for personal metrics."
|
|
5
5
|
authors = ["Daniel Soutar <danielsoutar144@gmail.com>"]
|
|
6
6
|
readme = "README.md"
|
|
@@ -11,6 +11,8 @@ python = "^3.10"
|
|
|
11
11
|
pandas = "^2.1.4"
|
|
12
12
|
requests = "^2.31.0"
|
|
13
13
|
matplotlib = "^3.8.3"
|
|
14
|
+
seaborn = "^0.13.2"
|
|
15
|
+
scipy = "^1.12.0"
|
|
14
16
|
|
|
15
17
|
|
|
16
18
|
[build-system]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|