dimwit 0.2.26__tar.gz → 0.2.28__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dimwit
3
- Version: 0.2.26
3
+ Version: 0.2.28
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
@@ -225,112 +225,149 @@ def get_moving_average_trend(data, k, padding="copy-on"):
225
225
  return rolling_average
226
226
 
227
227
 
228
- def get_basic_sleep_dataframe(
229
- ts: list[tuple],
230
- durations: list[str],
231
- target_sleep_start: str = "22:30:00",
232
- target_sleep_end: str = "06:30:00",
233
- ):
234
- N = len(ts)
235
- starts, ends = zip(*ts)
236
- all_aligned = len(starts) == len(ends) == len(durations) == N
237
- if not all_aligned:
238
- raise Exception(
239
- f"Unaligned start ({len(starts)} values), "
240
- f"end ({len(ends)} values), "
241
- f"duration ({len(durations)} values) columns"
228
+ # TODO: Figure out how to append to KDE plots so that the lines all start and
229
+ # end at the same place.
230
+ # TODO: Figure out how to 'project'/'predict' for recent data. Look into
231
+ # conditional density estimation.
232
+ def kde_over_all_categories(ts, events, bw_vals, exclude):
233
+ # Share the same axes across the various plots.
234
+ _, ax = plt.subplots(1)
235
+ for category, bw in zip(set(events), bw_vals):
236
+ if category in exclude:
237
+ continue
238
+ # Fetch all timestamps where the event matches the category.
239
+ cat_ts = list(
240
+ map(
241
+ lambda tup: tup[1],
242
+ filter(lambda tup: tup[0] == category, zip(events, ts)),
243
+ )
244
+ )
245
+ cat_timeframe = cat_ts[-1] - cat_ts[0]
246
+ total_timeframe = ts[-1] - ts[0]
247
+ # Exclude short-lived KDEs - these tend to skew the graphs.
248
+ if cat_timeframe < (0.3 * total_timeframe):
249
+ continue
250
+ df = pd.DataFrame(cat_ts, columns=["dates"])
251
+ df["ordinal"] = [x.toordinal() for x in df.dates]
252
+ ax = df["ordinal"].plot(
253
+ kind="kde",
254
+ bw_method=bw,
255
+ ax=ax,
256
+ label=category,
242
257
  )
243
- data = {
244
- "start_date": starts,
245
- "end_date": ends,
246
- "durations": durations,
247
- "target_sleep_start": [target_sleep_start] * N,
248
- "target_sleep_end": [target_sleep_end] * N,
249
- }
250
- df = pd.DataFrame(data)
251
- return df
252
258
 
259
+ ax.set_xlabel("Time")
260
+ ax.set_ylabel("Density")
261
+ ax.set_xlim(ts[0].toordinal(), ts[-1].toordinal())
262
+
263
+ # Take every other tick to reduce clutter.
264
+ x_ticks = ax.get_xticks()[::2]
265
+ ax.set_xticks(x_ticks)
266
+ xlabels = list(
267
+ map(
268
+ lambda x: dat.datetime.fromordinal(int(x)).strftime("%Y-%m-%d"),
269
+ x_ticks,
270
+ )
271
+ )
272
+ ax.set_xticklabels(xlabels)
273
+ ax.legend()
274
+ return ax
253
275
 
254
- def compute_rounded_matrix(ts, events, rounding_unit, rounding_size):
255
- """
256
- Compute a matrix where each (sorted, ascending) row corresponds to a
257
- timestamp, and the columns the types of events, rounding timestamps
258
- according to `rounding_unit` and `rounding_size`.
259
276
 
260
- Each entry corresponds to the number of events of a particular type
261
- occurring to the nearest rounded timestamp.
277
+ def kde_over_combined_categories(ts, bw):
278
+ df = pd.DataFrame(set(ts), columns=["dates"])
279
+ df["ordinal"] = [x.toordinal() for x in df.dates]
280
+ ax = df["ordinal"].plot(kind="kde", bw_method=bw, label="Combined")
262
281
 
263
- Any NaNs are converted to 0s.
264
- """
265
- data = {"value": [1] * len(events), "categories": events, "date": ts}
266
- df = pd.DataFrame(data)
267
- # Make sure to set the date column to datetimes recognisable by pandas.
268
- df["date"] = pd.to_datetime(df["date"], utc=True)
269
- rounding_string = str(rounding_size) + rounding_unit
270
- # Need to use dt field, or else more lengthy to work with native datetimes.
271
- df["date"] = df["date"].dt.round(rounding_string)
272
- df = df.pivot_table(
273
- index="date", columns="categories", values="value", aggfunc="sum"
282
+ ax.set_xlabel("Time")
283
+ ax.set_ylabel("Density")
284
+ ax.set_xlim(ts[0].toordinal(), ts[-1].toordinal())
285
+
286
+ # Take every other tick to reduce clutter.
287
+ x_ticks = ax.get_xticks()[::2]
288
+ ax.set_xticks(x_ticks)
289
+ xlabels = list(
290
+ map(
291
+ lambda x: dat.datetime.fromordinal(int(x)).strftime("%Y-%m-%d"),
292
+ x_ticks,
293
+ )
274
294
  )
275
- return df.fillna(0)
295
+ ax.set_xticklabels(xlabels)
296
+ ax.legend()
297
+ return ax
276
298
 
277
299
 
278
- def zero_padded_df(num_rows, num_cols, index):
279
- return pd.DataFrame([[0] * num_cols] * num_rows, index=index)
300
+ def day_kde_over_all_categories(ts, events, bw_vals, exclude):
301
+ # Share the same axes across the various plots.
302
+ _, ax = plt.subplots(1, figsize=(8, 6))
303
+ for category, bw in zip(set(events), bw_vals):
304
+ if category in exclude:
305
+ continue
306
+ # Fetch all timestamps where the event matches the category.
307
+ cat_ts = list(
308
+ map(
309
+ lambda tup: tup[1],
310
+ filter(lambda tup: tup[0] == category, zip(events, ts)),
311
+ )
312
+ )
313
+ cat_timeframe = cat_ts[-1] - cat_ts[0]
314
+ total_timeframe = ts[-1] - ts[0]
315
+ # Exclude short-lived KDEs - these tend to skew the graphs.
316
+ if cat_timeframe < (0.3 * total_timeframe):
317
+ continue
280
318
 
319
+ df = pd.DataFrame(cat_ts, columns=["dates"])
320
+ df["time"] = pd.to_datetime(df["dates"], utc=True).dt.time
321
+ df["time_delta"] = pd.to_timedelta([str(t) for t in df["time"]])
322
+ df["time_delta_float"] = df["time_delta"] // pd.Timedelta(minutes=1)
323
+ # Default size of the graph makes the labels too large, might as well
324
+ # have a bigger plot.
325
+ ax = df["time_delta_float"].plot(
326
+ kind="kde",
327
+ bw_method=bw,
328
+ label=category,
329
+ ax=ax,
330
+ )
281
331
 
282
- def pad_out_table(df, pad_t, unit, pad_before=True, pad_after=True):
283
- # Pad out df with entries before and after boundary rows, to ensure correct
284
- # same- (or zero-) padding.
285
- pad_t_days = pd.Timedelta(pad_t, unit=unit)
286
- pad_before_start = df.index[0] - pad_t_days
287
- pad_after_start = df.index[-1] + pd.Timedelta(1, unit=unit)
332
+ ax.set_xlabel("Time (Clock)")
333
+ ax.set_ylabel("Density")
288
334
 
289
- # The index in df cannot yet be assumed to be dense - so use a date range
290
- # with the earliest and latest entries as reference points.
291
- index_before = pd.date_range(pad_before_start, periods=pad_t, freq=unit)
292
- index_after = pd.date_range(pad_after_start, periods=pad_t, freq=unit)
335
+ ax.set_xlim(0, pd.Timedelta(days=1) // pd.Timedelta(minutes=1))
336
+ hours = [i for i in range(0, 25, 2)]
337
+ ax.set_xticks(
338
+ [i * 60 for i in hours], [str(i).rjust(2, "0") + ":00" for i in hours]
339
+ )
340
+ ax.set_yticks([])
341
+ ax.legend()
342
+ return ax
293
343
 
294
- num_rows, num_cols = pad_t, len(df.columns)
295
- if pad_before:
296
- df = pd.concat([zero_padded_df(num_rows, num_cols, index_before), df])
297
- if pad_after:
298
- df = pd.concat([zero_padded_df(num_rows, num_cols, index_after), df])
299
344
 
300
- return df
345
+ def day_kde_over_combined_categories(ts, bw):
346
+ df = pd.DataFrame(set(ts), columns=["dates"])
347
+ df["time"] = pd.to_datetime(df["dates"], utc=True).dt.time
348
+ df["time_delta"] = pd.to_timedelta([str(t) for t in df["time"]])
349
+ df["time_delta_float"] = df["time_delta"] // pd.Timedelta(minutes=1)
350
+ # Default size of the graph makes the labels too large, might as well have
351
+ # a bigger plot.
352
+ _, ax = plt.subplots(1, figsize=(8, 6))
353
+ ax = df["time_delta_float"].plot(
354
+ kind="kde",
355
+ bw_method=bw,
356
+ label="Combined",
357
+ ax=ax,
358
+ )
301
359
 
360
+ ax.set_xlabel("Time (Clock)")
361
+ ax.set_ylabel("Density")
302
362
 
303
- # TODO: Enable different window sizes for different columns.
304
- # Either this means returning a set of Series objects, or padding values in a
305
- # single dataframe.
306
- def compute_moving_average_matrix(
307
- ts, events, rounding_unit, rounding_size, window_size
308
- ):
309
- df = compute_rounded_matrix(ts, events, rounding_unit, rounding_size)
310
- categories = df.columns
311
-
312
- df_needs_padding = window_size > 1
313
- if df_needs_padding:
314
- pad_t = (window_size - 1) // 2
315
- df = pad_out_table(df, pad_t, rounding_unit)
316
-
317
- window_string = str(rounding_size) + rounding_unit
318
- dense_series = []
319
- for column in categories:
320
- # TODO: Fix annoying ImportError involving 'freq_to_period_freqstr'.
321
- sparse_column: pd.Series = df[column]
322
- dense_column = sparse_column.asfreq(freq=window_string)
323
- dense_column_no_nans = dense_column.fillna(0)
324
- dense_series.append(dense_column_no_nans)
325
-
326
- dense_df = pd.DataFrame(dense_series).T
327
-
328
- if not df_needs_padding:
329
- return dense_df
330
-
331
- averaged_df = dense_df.rolling(window_size, center=True).mean()
332
- # Skip the padding rows since they don't make much sense in the charts.
333
- return averaged_df.iloc[pad_t:-pad_t]
363
+ ax.set_xlim(0, pd.Timedelta(days=1) // pd.Timedelta(minutes=1))
364
+ hours = [i for i in range(0, 25, 2)]
365
+ ax.set_xticks(
366
+ [i * 60 for i in hours], [str(i).rjust(2, "0") + ":00" for i in hours]
367
+ )
368
+ ax.set_yticks([])
369
+ ax.legend()
370
+ return ax
334
371
 
335
372
 
336
373
  def get_aggregated_event_counts(ts, events, period):
@@ -118,18 +118,6 @@ def generate_cumulative_df_for_kth_latest_week(df, k):
118
118
  )
119
119
  start, end = pomodoro_date_range[0], pomodoro_date_range[-1]
120
120
 
121
- # If end later than latest available entry in df (i.e. start of the week),
122
- # then pad out df to generate zero entries.
123
- # if end > df.index[-1]:
124
- # num_entries = 0
125
- # latest_val = df.index[-1]
126
- # while end > latest_val:
127
- # latest_val += df.index.freq
128
- # num_entries += 1
129
- # df = dimwit.pad_out_table(
130
- # df, num_entries, df.index.freq, pad_before=False
131
- # )
132
-
133
121
  ref_policy = ReferencePolicy(45.0, 15.0, "(45+15)")
134
122
  cum_df = generate_cumulative_df(df, ref_policy, start, end)
135
123
 
@@ -101,9 +101,10 @@ def fill_sleep_entries(
101
101
  def create_sleep_df(ts, durations):
102
102
  starts, ends = zip(*ts)
103
103
 
104
- # Cast the timezone to UTC, ensuring that UTC offsets are removed.
105
- starts = [start.astimezone(dat.timezone.utc) for start in starts]
106
- ends = [end.astimezone(dat.timezone.utc) for end in ends]
104
+ # Replace the timezone with UTC, ensuring that UTC offsets are removed.
105
+ # TODO: For some reason this breaks older data until around mid-Nov 2023.
106
+ starts = [start.replace(tzinfo=dat.timezone.utc) for start in starts]
107
+ ends = [end.replace(tzinfo=dat.timezone.utc) for end in ends]
107
108
 
108
109
  start_dates = get_start_date(starts, ends, first_after_midnight=False)
109
110
  end_dates = start_dates.shift(1, freq="D")
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "dimwit"
3
- version = "0.2.26"
3
+ version = "0.2.28"
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"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes