dimwit 0.2.7__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.7 → dimwit-0.2.8}/PKG-INFO +3 -1
- {dimwit-0.2.7 → dimwit-0.2.8}/dimwit/__init__.py +2 -1
- dimwit-0.2.8/dimwit/airflow.py +277 -0
- {dimwit-0.2.7 → dimwit-0.2.8}/dimwit/main.py +3 -0
- {dimwit-0.2.7 → dimwit-0.2.8}/pyproject.toml +3 -1
- {dimwit-0.2.7 → dimwit-0.2.8}/README.md +0 -0
- {dimwit-0.2.7 → dimwit-0.2.8}/dimwit/air_pollution.py +0 -0
- {dimwit-0.2.7 → 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
|
|
@@ -356,6 +356,9 @@ def get_aggregated_event_counts(ts, events, period):
|
|
|
356
356
|
return grouped_df
|
|
357
357
|
|
|
358
358
|
|
|
359
|
+
# TODO: Add 'get all events where equal to' function. Do not want a dense df.
|
|
360
|
+
|
|
361
|
+
|
|
359
362
|
def populate_with_events(ax, events, from_date):
|
|
360
363
|
for event in events:
|
|
361
364
|
event_date, event_colour, event_style, event_label = event
|
|
@@ -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
|