acteval 0.1.0__py3-none-any.whl

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.
acteval/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ from acteval.evaluate import (
2
+ Evaluator as Evaluator,
3
+ )
4
+ from acteval.evaluate import (
5
+ compare as compare,
6
+ )
7
+ from acteval.evaluate import (
8
+ compare_splits as compare_splits,
9
+ )
10
+ from acteval.population import Population as Population
acteval/config.toml ADDED
@@ -0,0 +1,24 @@
1
+ [ngrams]
2
+ min_count = 3
3
+
4
+ [jobs.participations]
5
+ lengths = true
6
+ rates = true
7
+ pair_rates = true
8
+
9
+ [jobs.transitions]
10
+ "2-gram" = true
11
+ "3-gram" = true
12
+ "4-gram" = true
13
+
14
+ [jobs.timing]
15
+ start_times = true
16
+ durations = true
17
+ start_durations = true
18
+ joint_durations = true
19
+
20
+ [jobs.creativity]
21
+ enabled = true
22
+
23
+ [jobs.structural]
24
+ enabled = true
@@ -0,0 +1,2 @@
1
+ """Creativity metrics — diversity, novelty, conservatism, and homogeneity of
2
+ synthetic schedules relative to the observed population."""
File without changes
@@ -0,0 +1,88 @@
1
+ from pandas import DataFrame
2
+
3
+
4
+ def hash_schedule(schedule: DataFrame) -> list[str]:
5
+ """Hash a schedule. We first create strings of combined activities and durations.
6
+
7
+ Args:
8
+ schedule (DataFrame): Input schedule.
9
+
10
+ Returns:
11
+ str: hashed schedule.
12
+ """
13
+ act_hash = schedule.act.astype(str) + schedule.duration.astype(str)
14
+ return "".join(act_hash)
15
+
16
+
17
+ def hash_population(population: DataFrame) -> set[str]:
18
+ """Hash a population of sequences. We first create strings of combined activities and durations.
19
+ Then create a python set of these strings. This will remove duplicates.
20
+
21
+ Args:
22
+ population (DataFrame): Input population of sequences.
23
+
24
+ Returns:
25
+ set[str]: set of hashed sequences.
26
+ """
27
+ act_hash = population.act.astype(str) + population.duration.astype(str)
28
+ return set(act_hash.groupby(population.pid).apply("".join))
29
+
30
+
31
+ def diversity(population: DataFrame, hashed: set[str]) -> float:
32
+ """Measure the internal diversity of a population of sequences. This is the ratio of unique
33
+ sequences to the total number of sequences.
34
+
35
+ Args:
36
+ population (DataFrame): Input population of sequences.
37
+ hashed (set[str]): Hashed population of sequences.
38
+
39
+ Returns:
40
+ float: Diversity of the population.
41
+ """
42
+ n = population.pid.nunique()
43
+ if n == 0:
44
+ return 0
45
+ unique = len(hashed)
46
+ return unique / n
47
+
48
+
49
+ def homogeneity(population: DataFrame, hashed: set[str]) -> float:
50
+ """Measure the internal homogeneity of a population of sequences. This is 1-diversity.
51
+
52
+ Args:
53
+ population (DataFrame): Input population of sequences.
54
+ hashed (set[str]): Hashed population of sequences.
55
+
56
+ Returns:
57
+ float: Homogeneity of the synthetic sample.
58
+ """
59
+ return 1 - diversity(population, hashed)
60
+
61
+
62
+ def novelty(observed_hashed: set[str], synthetic_hashed: set[str]) -> float:
63
+ """Measure the novelty of a population by comparing it to an observed population.
64
+
65
+ Args:
66
+ observed_hashed (set[str]): Hashed observed population.
67
+ synthetic_hashed (set[str]): Hashed synthetic population.
68
+
69
+ Returns:
70
+ float: Novelty of the synthetic population.
71
+ """
72
+ n = len(synthetic_hashed)
73
+ if n == 0:
74
+ return 0
75
+ return len(synthetic_hashed - observed_hashed) / n
76
+
77
+
78
+ def conservatism(observed_hashed: set[str], synthetic_hashed: set[str]) -> float:
79
+ """Measure the conservatism of a population as 1-novelty.
80
+
81
+ Args:
82
+ observed_hashed (set[str]): Hashed observed population.
83
+ synthetic_hashed (set[str]): Hashed synthetic population.
84
+
85
+ Returns:
86
+ float: Conservatism of the synthetic population.
87
+ """
88
+ return 1 - novelty(observed_hashed, synthetic_hashed)
@@ -0,0 +1,2 @@
1
+ """Density estimation jobs — measures how well synthetic activity distributions
2
+ (participation rates, timing, n-gram transitions) match the observed population."""
File without changes
@@ -0,0 +1,101 @@
1
+ from datetime import datetime, timedelta
2
+ from typing import Optional
3
+
4
+ from matplotlib import pyplot as plt
5
+ from matplotlib.patches import Patch
6
+ from pandas import DataFrame
7
+
8
+ from acteval.density.features.frequency import binned_activity_density
9
+
10
+
11
+ def frequency_plots(observed: DataFrame, ys: Optional[dict[DataFrame]], **kwargs):
12
+ if ys is None:
13
+ ys = dict()
14
+ acts = list(observed.act.value_counts(ascending=False).index)
15
+ class_map = {n: i for i, n in enumerate(acts)}
16
+
17
+ n_plots = len(ys) + 2
18
+ ratios = [1 for _ in range(n_plots)]
19
+ ratios[-1] = 0.3
20
+
21
+ cmap = kwargs.pop("cmap", None)
22
+ if cmap is None:
23
+ cmap = plt.cm.Set3
24
+ colors = cmap.colors
25
+ factor = (len(acts) // len(colors)) + 1
26
+ cmap = dict(zip(acts, colors * factor))
27
+
28
+ fig, axs = plt.subplots(
29
+ sharex=True,
30
+ sharey=True,
31
+ nrows=1,
32
+ ncols=n_plots,
33
+ constrained_layout=True,
34
+ figsize=kwargs.pop("figsize", (15, 4)),
35
+ gridspec_kw={"width_ratios": ratios},
36
+ )
37
+
38
+ name = kwargs.pop("observed_title", "Observed")
39
+
40
+ plot_agg_acts(name, observed, class_map, ax=axs[0], legend=False, **kwargs)
41
+
42
+ # now deal with ys
43
+ for i, (name, y) in enumerate(ys.items()):
44
+ ax = axs[i + 1]
45
+ plot_agg_acts(name, y, class_map, ax=ax, legend=False, **kwargs)
46
+
47
+ # legend
48
+ elements = [Patch(facecolor=cmap[act], label=act.title()) for act in acts]
49
+ axs[-1].axis("off")
50
+ axs[-1].legend(handles=elements, loc="center left", frameon=False)
51
+
52
+ return fig
53
+
54
+
55
+ def plot_agg_acts(
56
+ name: str,
57
+ population: DataFrame,
58
+ class_map: dict,
59
+ duration: int = 1440,
60
+ step: int = 10,
61
+ ax=None,
62
+ legend=True,
63
+ **kwargs,
64
+ ):
65
+ interval = kwargs.pop("interval", 240)
66
+ bins = binned_activity_density(
67
+ population, duration=duration, step=step, class_map=class_map
68
+ )
69
+ columns = list(class_map.keys())
70
+ totals = bins.sum(0)
71
+ sorted_cols = [x for _, x in sorted(zip(totals, columns))]
72
+ df = DataFrame(bins, columns=columns)[sorted_cols]
73
+ df.index = [
74
+ datetime(2021, 11, 1, 0) + timedelta(minutes=i * step)
75
+ for i in range(len(df.index))
76
+ ]
77
+ fig = df.plot(kind="bar", stacked=True, width=1, ax=ax, legend=legend, **kwargs)
78
+ if legend:
79
+ ax.legend(loc="upper right")
80
+ ax = fig.axes
81
+ labels = ["" for _ in range(len(df.index))]
82
+
83
+ labels[:: int(interval / step)] = [x.strftime("%H:%M") for x in df.index][
84
+ :: int(interval / step)
85
+ ]
86
+ ax.set_xticks(
87
+ [i / step for i in [0, 240, 480, 720, 960, 1200, 1440]],
88
+ labels=["00:00", "04:00", "08:00", "12:00", "16:00", "20:00", "24:00"],
89
+ rotation=90,
90
+ fontsize=8,
91
+ )
92
+
93
+ ax.spines["top"].set_visible(False)
94
+ ax.spines["right"].set_visible(False)
95
+ ax.spines["bottom"].set_visible(False)
96
+ ax.spines["left"].set_visible(False)
97
+
98
+ ax.set_xlabel("Time of day")
99
+ ax.set_ylabel("Activity Proportion")
100
+ ax.set_title(name)
101
+ return ax
@@ -0,0 +1,269 @@
1
+ from typing import Optional, Tuple
2
+
3
+ from matplotlib import colormaps, patches
4
+ from matplotlib import pyplot as plt
5
+ from matplotlib.figure import Axes, Figure
6
+ from pandas import DataFrame
7
+
8
+
9
+ def times_distributions_plot(
10
+ observed: DataFrame, ys: Optional[dict[str, DataFrame]], **kwargs
11
+ ) -> Figure:
12
+ ratios = [1 for _ in range(4)]
13
+ ratios[0] = 0.2
14
+ fig, axs = plt.subplots(
15
+ 4,
16
+ observed.act.nunique(),
17
+ figsize=kwargs.pop("figsize", (12, 5)),
18
+ sharex=True,
19
+ sharey=False,
20
+ # tight_layout=True,
21
+ constrained_layout=True,
22
+ gridspec_kw={"height_ratios": ratios},
23
+ )
24
+ acts = list(observed.act.value_counts(ascending=False).index)
25
+ name = kwargs.pop("observed_title", "Observed")
26
+ _times_plot(name, observed, acts, axs=axs)
27
+ if ys is None:
28
+ return fig
29
+ for name, y in ys.items():
30
+ _times_plot(name, y, acts, axs=axs)
31
+ for ax in axs[0]:
32
+ ax.tick_params(axis="x", which="both", length=0.0)
33
+ handles, labels = axs[1][0].get_legend_handles_labels()
34
+ fig.legend(
35
+ handles,
36
+ labels,
37
+ loc="upper center",
38
+ fontsize=9,
39
+ ncol=len(ys) + 1,
40
+ frameon=False,
41
+ )
42
+ return fig
43
+
44
+
45
+ def _times_plot(
46
+ name: str,
47
+ population: DataFrame,
48
+ acts: list[str],
49
+ axs: Axes,
50
+ xmin: int = 0,
51
+ xmax: int = 1440,
52
+ step: int = 30,
53
+ **kwargs,
54
+ ) -> Tuple[Figure, Axes]:
55
+ starts = population.groupby("act", observed=False).start
56
+ ends = population.groupby("act", observed=False).end
57
+ durations = population.groupby("act", observed=False).duration
58
+ bins = list(range(xmin, xmax, step))
59
+
60
+ for i, act in enumerate(acts):
61
+ if act not in population.act.unique():
62
+ continue
63
+ axs[0][i].spines["top"].set_visible(False)
64
+ axs[0][i].spines["right"].set_visible(False)
65
+ axs[0][i].spines["bottom"].set_visible(False)
66
+ axs[0][i].spines["left"].set_visible(False)
67
+ axs[0][i].set_xticks([])
68
+ axs[0][i].set_yticks([])
69
+
70
+ axs[1][i].set_title(act.title(), fontsize="small")
71
+ start_group = starts.get_group(act)
72
+ if len(start_group) < 5:
73
+ continue
74
+ axs[1][i].hist(
75
+ starts.get_group(act),
76
+ bins=bins,
77
+ density=True,
78
+ histtype="step",
79
+ label=name,
80
+ linewidth=1.4,
81
+ **kwargs,
82
+ )
83
+ axs[1][i].set_xlim(xmin, xmax)
84
+ axs[1][i].set_yticklabels([])
85
+ axs[1][i].set(ylabel=None)
86
+ axs[1][i].set_xticks([])
87
+ axs[1][i].set_yticks([])
88
+
89
+ axs[2][i].hist(
90
+ ends.get_group(act),
91
+ bins=bins,
92
+ density=True,
93
+ histtype="step",
94
+ label=name,
95
+ linewidth=1.4,
96
+ **kwargs,
97
+ )
98
+ axs[2][i].set_xlim(xmin, xmax)
99
+ axs[2][i].set_yticklabels([])
100
+ axs[2][i].set(ylabel=None)
101
+ axs[2][i].set_xticks([])
102
+ axs[2][i].set_yticks([])
103
+
104
+ axs[3][i].hist(
105
+ durations.get_group(act),
106
+ bins=bins,
107
+ density=True,
108
+ histtype="step",
109
+ label=name,
110
+ linewidth=1.4,
111
+ **kwargs,
112
+ )
113
+ axs[3][i].set_xlim(xmin, xmax)
114
+ axs[3][i].set_yticklabels([])
115
+ axs[3][i].set(ylabel=None)
116
+ axs[3][i].set_xticks(
117
+ [0, 240, 480, 720, 960, 1200, 1440],
118
+ labels=[
119
+ "00:00",
120
+ "04:00",
121
+ "08:00",
122
+ "12:00",
123
+ "16:00",
124
+ "20:00",
125
+ "24:00",
126
+ ],
127
+ rotation=90,
128
+ fontsize=8,
129
+ )
130
+ axs[3][i].set_yticks([])
131
+ axs[3][i].set_xlabel("Time/Duration", fontsize=8)
132
+
133
+ axs[1][0].set_ylabel("Start Time\nDensities", fontsize=10)
134
+ axs[2][0].set_ylabel("End Time\nDensities", fontsize=10)
135
+ axs[3][0].set_ylabel("Duration\nDensities", fontsize=10)
136
+
137
+
138
+ def joint_time_distributions_plot(
139
+ observed: DataFrame, ys: Optional[dict[DataFrame]], **kwargs
140
+ ) -> Figure:
141
+ if ys is None:
142
+ ys = dict()
143
+ acts = list(observed.act.value_counts(ascending=False).index)
144
+ n_acts = len(acts)
145
+ rows = len(ys) + 2
146
+ ratios = [1 for _ in range(rows)]
147
+ ratios[0] = 0.2
148
+
149
+ cmaps = kwargs.pop("cmaps", {})
150
+ legend = []
151
+ legend_colours = []
152
+
153
+ fig, axs = plt.subplots(
154
+ rows,
155
+ observed.act.nunique(),
156
+ figsize=kwargs.pop("figsize", (12, 5)),
157
+ sharex=False,
158
+ sharey=False,
159
+ constrained_layout=True,
160
+ gridspec_kw={"height_ratios": ratios},
161
+ )
162
+
163
+ # deal with observed first
164
+ name = kwargs.pop("observed_title", "Observed")
165
+
166
+ legend.append(name)
167
+ cmap = cmaps.get(0, "Blues")
168
+ lcolours = colormaps[cmap]([0, 0.5, 1])
169
+ legend_colours.append(lcolours[int(len(lcolours) / 2)])
170
+
171
+ _joint_time_plot(observed, axs[1], acts, cmap=cmap)
172
+
173
+ # now deal with ys
174
+ for i, (name, y) in enumerate(ys.items()):
175
+
176
+ legend.append(name)
177
+ cmap = cmaps.get(i + 1, "Reds")
178
+ lcolours = colormaps[cmap]([0, 0.5, 1])
179
+ legend_colours.append(lcolours[int(len(lcolours) / 2)])
180
+
181
+ _joint_time_plot(y, axs[i + 2], acts, cmap=cmap)
182
+
183
+ # xlabel on bottom row
184
+ for ax in axs[-1]:
185
+ ax.set_xlabel("Start times", fontsize=8)
186
+ ax.set_xticks(
187
+ [240, 480, 720, 960, 1200, 1440],
188
+ labels=["04:00", "08:00", "12:00", "16:00", "20:00", "24:00"],
189
+ rotation=90,
190
+ fontsize=8,
191
+ )
192
+
193
+ # acts
194
+ for ax, act in zip(axs[1], acts):
195
+ ax.set_title(act.title(), fontsize=9)
196
+
197
+ # deal with legend
198
+ for i in range(n_acts):
199
+ axs[0][i].spines["top"].set_visible(False)
200
+ axs[0][i].spines["right"].set_visible(False)
201
+ axs[0][i].spines["bottom"].set_visible(False)
202
+ axs[0][i].spines["left"].set_visible(False)
203
+ axs[0][i].set_xticks([])
204
+ axs[0][i].set_yticks([])
205
+ for ax in axs[0]:
206
+ ax.tick_params(axis="x", which="both", length=0.0)
207
+
208
+ handles = [
209
+ patches.Patch(color=c, label=lab) for c, lab in zip(legend_colours, legend)
210
+ ]
211
+ fig.legend(
212
+ handles=handles,
213
+ loc="upper center",
214
+ fontsize=9,
215
+ ncol=len(ys) + 1,
216
+ frameon=False,
217
+ )
218
+
219
+ return fig
220
+
221
+
222
+ def _joint_time_plot(
223
+ population: DataFrame,
224
+ axs: Axes,
225
+ acts: list[str],
226
+ cmap: str,
227
+ xmin: int = 240,
228
+ xmax: int = 1441,
229
+ ymin: int = 0,
230
+ ymax: int = 960,
231
+ xstep: int = 30,
232
+ ystep: int = 30,
233
+ ):
234
+ starts = population.groupby("act", observed=False).start
235
+ durations = population.groupby("act", observed=False).duration
236
+
237
+ start_bins = list(range(xmin, xmax, xstep))
238
+ duration_bins = list(range(ymin, ymax, ystep))
239
+
240
+ for i, act in enumerate(acts):
241
+ axs[i].set_xticks([])
242
+ axs[i].set_yticks([])
243
+ if act not in population.act.unique():
244
+ continue
245
+ act_starts = starts.get_group(act)
246
+ act_durations = durations.get_group(act)
247
+ axs[i].hist2d(
248
+ x=act_starts,
249
+ y=act_durations,
250
+ bins=(start_bins, duration_bins),
251
+ cmap=cmap,
252
+ )
253
+ ylabel = "Durations"
254
+ axs[0].set_ylabel(ylabel, fontsize=8)
255
+ axs[0].set_yticks(
256
+ [0, 120, 240, 360, 480, 600, 720, 840, 960],
257
+ labels=[
258
+ "00:00",
259
+ "02:00",
260
+ "04:00",
261
+ "06:00",
262
+ "08:00",
263
+ "10:00",
264
+ "12:00",
265
+ "14:00",
266
+ "16:00",
267
+ ],
268
+ fontsize=8,
269
+ )
@@ -0,0 +1,93 @@
1
+ from typing import Optional, Tuple
2
+
3
+ from matplotlib import pyplot as plt
4
+ from matplotlib.axes import Axes
5
+ from matplotlib.colors import ListedColormap as CMap
6
+ from matplotlib.figure import Figure
7
+ from matplotlib.patches import Patch
8
+ from pandas import DataFrame
9
+
10
+ from acteval.density.features.transitions import sequence_probs
11
+
12
+
13
+ def sequence_prob_plot(
14
+ observed: DataFrame, ys: Optional[dict[DataFrame]], **kwargs
15
+ ) -> Figure:
16
+ acts = list(observed.act.value_counts(ascending=False).index)
17
+ cmap = kwargs.pop("cmap", None)
18
+ if cmap is None:
19
+ cmap = plt.cm.Set3
20
+ colors = cmap.colors
21
+ factor = (len(acts) // len(colors)) + 1
22
+ cmap = dict(zip(acts, colors * factor))
23
+
24
+ n_plots = len(ys) + 2
25
+ ratios = [1 for _ in range(n_plots)]
26
+ ratios[-1] = 0.3
27
+
28
+ fig, axs = plt.subplots(
29
+ 1,
30
+ n_plots,
31
+ figsize=kwargs.pop("figsize", (12, 5)),
32
+ sharex=True,
33
+ sharey=True,
34
+ # tight_layout=True,
35
+ constrained_layout=True,
36
+ gridspec_kw={"width_ratios": ratios},
37
+ )
38
+ acts = list(observed.act.value_counts(ascending=False).index)
39
+ name = kwargs.pop("observed_title", "Observed")
40
+ _probs_plot(name, observed, ax=axs[0], cmap=cmap, ylabel=True)
41
+
42
+ if ys is None:
43
+ return fig
44
+ for i, (name, y) in enumerate(ys.items()):
45
+ _probs_plot(name, y, ax=axs[i + 1], cmap=cmap)
46
+ axs[i + 1].set_title(name)
47
+
48
+ elements = [Patch(facecolor=cmap[act], label=act.title()) for act in acts]
49
+ axs[-1].axis("off")
50
+ axs[-1].legend(handles=elements, loc="center left", frameon=False)
51
+
52
+ return fig
53
+
54
+
55
+ def _probs_plot(
56
+ name: str,
57
+ population: DataFrame,
58
+ cmap: Optional[CMap],
59
+ ax=Axes,
60
+ ylabel=False,
61
+ ) -> Tuple[Figure, Axes]:
62
+ probs = sequence_probs(population)
63
+ accumulated = probs[::-1].cumsum()[::-1]
64
+
65
+ ys = []
66
+ widths = []
67
+ heights = []
68
+ lefts = []
69
+ cols = []
70
+ for idx, p, ap in zip(probs.index, probs, accumulated):
71
+ seq = idx[1].split(">")
72
+ width = 1 / len(seq)
73
+ for i, act in enumerate(seq):
74
+ ys.append(ap - p)
75
+ widths.append(width)
76
+ heights.append(p)
77
+ lefts.append(i * width)
78
+ cols.append(cmap[act])
79
+
80
+ ax.barh(y=ys, width=widths, height=heights, left=lefts, color=cols, align="edge")
81
+ ax.hlines(ys, xmin=0, xmax=1, color="white", linewidth=0.1)
82
+
83
+ ax.spines["top"].set_visible(False)
84
+ ax.spines["right"].set_visible(False)
85
+ ax.spines["bottom"].set_visible(False)
86
+ ax.spines["left"].set_visible(False)
87
+ ax.set_xlabel("Activity\nSequence")
88
+ if ylabel:
89
+ ax.set_ylabel("Sequence Proportion")
90
+ ax.set_xticklabels([])
91
+ ax.set_xticks([])
92
+ ax.set_title(name)
93
+ return ax
File without changes
@@ -0,0 +1,97 @@
1
+ """Pure-numpy discretisation helpers (no torch dependency)."""
2
+
3
+ from typing import Iterable
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+
8
+
9
+ def descretise_population(
10
+ data: pd.DataFrame, duration: int, step_size: int, class_map: dict
11
+ ) -> np.ndarray:
12
+ """Convert given population of activity traces into array [P, C, H, W].
13
+
14
+ P is the population size.
15
+ C (channel) is length 1.
16
+ H is time steps.
17
+ W is a one-hot encoding of activity type.
18
+
19
+ Args:
20
+ data (pd.DataFrame): Schedule DataFrame with pid, act, start, end columns.
21
+ duration (int): Total duration in time units.
22
+ step_size (int): Step size for discretisation.
23
+ class_map (dict): Mapping from activity string to integer class index.
24
+
25
+ Returns:
26
+ np.ndarray: Array of shape [P, C, H, W].
27
+ """
28
+ persons = data.pid.nunique()
29
+ num_classes = len(class_map)
30
+ steps = duration // step_size
31
+ encoded = np.zeros((persons, steps, num_classes, 1), dtype=np.float32)
32
+
33
+ for pid, (_, trace) in enumerate(data.groupby("pid")):
34
+ trace_encoding = descretise_trace(
35
+ acts=trace.act,
36
+ starts=trace.start,
37
+ ends=trace.end,
38
+ length=duration,
39
+ class_map=class_map,
40
+ )
41
+ trace_encoding = down_sample(trace_encoding, step_size)
42
+ trace_encoding = one_hot(trace_encoding, num_classes)
43
+ trace_encoding = trace_encoding.reshape(steps, num_classes, 1)
44
+ encoded[pid] = trace_encoding # [B, H, W, C]
45
+ encoded = encoded.transpose(0, 3, 1, 2) # [B, C, H, W]
46
+ return encoded
47
+
48
+
49
+ def descretise_trace(
50
+ acts: Iterable[str],
51
+ starts: Iterable[int],
52
+ ends: Iterable[int],
53
+ length: int,
54
+ class_map: dict,
55
+ ) -> np.ndarray:
56
+ """Create categorical encoding from ranges with step of 1.
57
+
58
+ Args:
59
+ acts (Iterable[str]): Activity labels.
60
+ starts (Iterable[int]): Start times.
61
+ ends (Iterable[int]): End times.
62
+ length (int): Total length of encoding.
63
+ class_map (dict): Mapping from activity string to integer class index.
64
+
65
+ Returns:
66
+ np.ndarray: 1D categorical encoding of length `length`.
67
+ """
68
+ encoding = np.zeros((length), dtype=np.int8)
69
+ for act, start, end in zip(acts, starts, ends):
70
+ encoding[start:end] = class_map[act]
71
+ return encoding
72
+
73
+
74
+ def down_sample(array: np.ndarray, step: int) -> np.ndarray:
75
+ """Down-sample by stepping through given array.
76
+
77
+ Args:
78
+ array (np.ndarray): Input array.
79
+ step (int): Step size.
80
+
81
+ Returns:
82
+ np.ndarray: Down-sampled array.
83
+ """
84
+ return array[::step]
85
+
86
+
87
+ def one_hot(target: np.ndarray, num_classes: int) -> np.ndarray:
88
+ """One hot encoding of given categorical array.
89
+
90
+ Args:
91
+ target (np.ndarray): Categorical array.
92
+ num_classes (int): Number of classes.
93
+
94
+ Returns:
95
+ np.ndarray: One-hot encoded array.
96
+ """
97
+ return np.eye(num_classes)[target]