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 +10 -0
- acteval/config.toml +24 -0
- acteval/creativity/__init__.py +2 -0
- acteval/creativity/features/__init__.py +0 -0
- acteval/creativity/features/creativity.py +88 -0
- acteval/density/__init__.py +2 -0
- acteval/density/describe/__init__.py +0 -0
- acteval/density/describe/frequency.py +101 -0
- acteval/density/describe/times.py +269 -0
- acteval/density/describe/transitions.py +93 -0
- acteval/density/features/__init__.py +0 -0
- acteval/density/features/_discretise.py +97 -0
- acteval/density/features/frequency.py +51 -0
- acteval/density/features/participation.py +192 -0
- acteval/density/features/pid_features.py +73 -0
- acteval/density/features/times.py +171 -0
- acteval/density/features/transitions.py +241 -0
- acteval/density/features/utils.py +197 -0
- acteval/distance/__init__.py +16 -0
- acteval/distance/scalar.py +68 -0
- acteval/distance/wasserstein.py +200 -0
- acteval/evaluate.py +811 -0
- acteval/filters.py +16 -0
- acteval/jobs.py +215 -0
- acteval/ops.py +49 -0
- acteval/population.py +165 -0
- acteval/structural/__init__.py +2 -0
- acteval/structural/features/__init__.py +0 -0
- acteval/structural/features/structural.py +210 -0
- acteval-0.1.0.dist-info/METADATA +12 -0
- acteval-0.1.0.dist-info/RECORD +32 -0
- acteval-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy import arange, ndarray
|
|
3
|
+
from pandas import DataFrame
|
|
4
|
+
|
|
5
|
+
from acteval.density.features._discretise import descretise_population
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def count_schedules(population: DataFrame) -> int:
|
|
9
|
+
count = population.pid.nunique()
|
|
10
|
+
return {"all": (np.array([count]), np.array([1]))}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def binned_activity_count(
|
|
14
|
+
population: DataFrame, class_map: dict, duration: int = 1440, step: int = 15
|
|
15
|
+
) -> ndarray:
|
|
16
|
+
return descretise_population(
|
|
17
|
+
population, duration=duration, step_size=step, class_map=class_map
|
|
18
|
+
).sum(0)[0, :, :]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def binned_activity_density(
|
|
22
|
+
population: DataFrame, class_map: dict, duration: int = 1440, step: int = 15
|
|
23
|
+
) -> ndarray:
|
|
24
|
+
return descretise_population(
|
|
25
|
+
population, duration=duration, step_size=step, class_map=class_map
|
|
26
|
+
).mean(0)[0, :, :]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def activity_frequencies(
|
|
30
|
+
population: DataFrame, duration: int = 1440, step: int = 15
|
|
31
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
32
|
+
index_to_acts = {i: a for i, a in enumerate(population.act.unique())}
|
|
33
|
+
class_map = {a: i for i, a in index_to_acts.items()}
|
|
34
|
+
bins = binned_activity_count(
|
|
35
|
+
population=population, class_map=class_map, duration=duration, step=step
|
|
36
|
+
)
|
|
37
|
+
support = arange(0, 1, step / duration)
|
|
38
|
+
freqs = {act: (support, bins[:, i]) for act, i in class_map.items()}
|
|
39
|
+
return freqs
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def activity_densities(
|
|
43
|
+
population: DataFrame, duration: int = 1440, step: int = 15
|
|
44
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
45
|
+
index_to_acts = {i: a for i, a in enumerate(population.act.unique())}
|
|
46
|
+
class_map = {a: i for i, a in index_to_acts.items()}
|
|
47
|
+
bins = binned_activity_density(
|
|
48
|
+
population=population, class_map=class_map, duration=duration, step=step
|
|
49
|
+
)
|
|
50
|
+
support = arange(0, 1, step / duration)
|
|
51
|
+
return {act: (support, bins[:, i]) for act, i in class_map.items()}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy import array, ndarray
|
|
3
|
+
|
|
4
|
+
from acteval.density.features.pid_features import PidFeatures
|
|
5
|
+
from acteval.density.features.utils import (
|
|
6
|
+
_count_matrix,
|
|
7
|
+
weighted_features,
|
|
8
|
+
)
|
|
9
|
+
from acteval.population import Population
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def participation_prob_by_act(
|
|
13
|
+
population: Population,
|
|
14
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
15
|
+
"""Calculate the participations by activity for a given population.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
population (Population): The population data.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
dict[str, tuple[array, array]]: A dictionary containing the participation for each activity.
|
|
22
|
+
"""
|
|
23
|
+
matrix = population.count_matrix
|
|
24
|
+
participated = (matrix > 0).sum(axis=0)
|
|
25
|
+
n = population.n
|
|
26
|
+
return {
|
|
27
|
+
act: (array([0, 1]), array([n - participated[j], participated[j]]))
|
|
28
|
+
for j, act in enumerate(population.unique_acts)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def participation_rates(
|
|
33
|
+
population: Population,
|
|
34
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
35
|
+
return weighted_features({"all": population.pid_counts})
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def participation_rates_by_act(
|
|
39
|
+
population: Population,
|
|
40
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
41
|
+
matrix = population.count_matrix
|
|
42
|
+
return weighted_features(
|
|
43
|
+
{act: matrix[:, j] for j, act in enumerate(population.unique_acts)}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def participation_rates_by_seq_act(
|
|
48
|
+
population: Population,
|
|
49
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
50
|
+
matrix, _, unique_keys = _count_matrix(population.pids, population.seq_key)
|
|
51
|
+
return weighted_features({k: matrix[:, j] for j, k in enumerate(unique_keys)})
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def participation_rates_by_act_enum(
|
|
55
|
+
population: Population,
|
|
56
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
57
|
+
matrix, _, unique_keys = _count_matrix(population.pids, population.act_enum_key)
|
|
58
|
+
return weighted_features({k: matrix[:, j] for j, k in enumerate(unique_keys)})
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def calc_pair_prob(act_counts, pair):
|
|
62
|
+
a, b = pair
|
|
63
|
+
if a == b:
|
|
64
|
+
return (act_counts[a] > 1).sum()
|
|
65
|
+
return ((act_counts[a] > 0) & (act_counts[b] > 0)).sum()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def calc_pair_rate(act_counts, pair):
|
|
69
|
+
a, b = pair
|
|
70
|
+
if a == b:
|
|
71
|
+
return ((act_counts[a] / 2).astype(int)).value_counts().to_dict()
|
|
72
|
+
return ((act_counts[[a, b]].min(axis=1) / 2).astype(int)).value_counts().to_dict()
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def combinations_with_replacement(
|
|
76
|
+
targets: list, length: int, prev_array=[]
|
|
77
|
+
) -> list[list]:
|
|
78
|
+
"""Returns all possible combinations of elements in the input array with replacement,
|
|
79
|
+
where each combination has a length of tuple_length.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
targets (list): The input array to generate combinations from.
|
|
83
|
+
length (int): The length of each combination.
|
|
84
|
+
prev_array (list, optional): The previous array generated in the recursion. Defaults to [].
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
list: A list of all possible combinations of elements in the input array with replacement.
|
|
88
|
+
"""
|
|
89
|
+
if len(prev_array) == length:
|
|
90
|
+
return [prev_array]
|
|
91
|
+
combs = []
|
|
92
|
+
for i, val in enumerate(targets):
|
|
93
|
+
prev_array_extended = prev_array.copy()
|
|
94
|
+
prev_array_extended.append(val)
|
|
95
|
+
combs += combinations_with_replacement(targets[i:], length, prev_array_extended)
|
|
96
|
+
return combs
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def joint_participation_prob(
|
|
100
|
+
population: Population,
|
|
101
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
102
|
+
"""Calculate the participation prob for all pairs of activities in the given population.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
population (Population): A Population containing the population data.
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
dict: A dictionary containing the participation probability for all pairs of activities.
|
|
109
|
+
"""
|
|
110
|
+
matrix = population.count_matrix
|
|
111
|
+
unique_acts = population.unique_acts
|
|
112
|
+
act_list = list(unique_acts)
|
|
113
|
+
act_idx = {a: i for i, a in enumerate(act_list)}
|
|
114
|
+
n = matrix.shape[0]
|
|
115
|
+
pairs = combinations_with_replacement(act_list, 2)
|
|
116
|
+
metric = {}
|
|
117
|
+
for pair in pairs:
|
|
118
|
+
ai, bi = act_idx[pair[0]], act_idx[pair[1]]
|
|
119
|
+
if pair[0] == pair[1]:
|
|
120
|
+
p = int((matrix[:, ai] > 1).sum())
|
|
121
|
+
else:
|
|
122
|
+
p = int(((matrix[:, ai] > 0) & (matrix[:, bi] > 0)).sum())
|
|
123
|
+
metric["+".join(pair)] = (array([0, 1]), array([n - p, p]))
|
|
124
|
+
return metric
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def joint_participation_rate(
|
|
128
|
+
population: Population,
|
|
129
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
130
|
+
"""Calculate the participation rate for all pairs of activities in the given population.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
population (Population): A Population containing the population data.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
dict: A dictionary containing the participation rate for all pairs of activities.
|
|
137
|
+
"""
|
|
138
|
+
matrix = population.count_matrix
|
|
139
|
+
unique_acts = population.unique_acts
|
|
140
|
+
act_list = list(unique_acts)
|
|
141
|
+
act_idx = {a: i for i, a in enumerate(act_list)}
|
|
142
|
+
pairs = combinations_with_replacement(act_list, 2)
|
|
143
|
+
metric = {}
|
|
144
|
+
for pair in pairs:
|
|
145
|
+
ai, bi = act_idx[pair[0]], act_idx[pair[1]]
|
|
146
|
+
if pair[0] == pair[1]:
|
|
147
|
+
vals = matrix[:, ai] // 2
|
|
148
|
+
else:
|
|
149
|
+
vals = np.minimum(matrix[:, ai], matrix[:, bi]) // 2
|
|
150
|
+
keys, counts = np.unique(vals, return_counts=True)
|
|
151
|
+
metric["+".join(pair)] = (keys, counts)
|
|
152
|
+
return metric
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ---------------------------------------------------------------------------
|
|
156
|
+
# Per-pid variants — return PidFeatures for efficient subsetting
|
|
157
|
+
# ---------------------------------------------------------------------------
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def participation_rates_by_act_per_pid(
|
|
161
|
+
population: Population,
|
|
162
|
+
) -> PidFeatures:
|
|
163
|
+
"""Per-pid participation rates by activity.
|
|
164
|
+
|
|
165
|
+
Each key maps to ``(count_per_person, pids)`` where *pids* is shared
|
|
166
|
+
across all keys (the dense pid range).
|
|
167
|
+
"""
|
|
168
|
+
matrix = population.count_matrix
|
|
169
|
+
pids = np.arange(population.n)
|
|
170
|
+
data = {act: (matrix[:, j], pids) for j, act in enumerate(population.unique_acts)}
|
|
171
|
+
return PidFeatures(data=data, bin_size=None, factor=1)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def joint_participation_rate_per_pid(
|
|
175
|
+
population: Population,
|
|
176
|
+
) -> PidFeatures:
|
|
177
|
+
"""Per-pid joint participation rates for all activity pairs."""
|
|
178
|
+
matrix = population.count_matrix
|
|
179
|
+
unique_acts = population.unique_acts
|
|
180
|
+
act_list = list(unique_acts)
|
|
181
|
+
act_idx = {a: i for i, a in enumerate(act_list)}
|
|
182
|
+
pids = np.arange(population.n)
|
|
183
|
+
pairs = combinations_with_replacement(act_list, 2)
|
|
184
|
+
data = {}
|
|
185
|
+
for pair in pairs:
|
|
186
|
+
ai, bi = act_idx[pair[0]], act_idx[pair[1]]
|
|
187
|
+
if pair[0] == pair[1]:
|
|
188
|
+
vals = matrix[:, ai] // 2
|
|
189
|
+
else:
|
|
190
|
+
vals = np.minimum(matrix[:, ai], matrix[:, bi]) // 2
|
|
191
|
+
data["+".join(pair)] = (vals, pids)
|
|
192
|
+
return PidFeatures(data=data, bin_size=None, factor=1)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""PidFeatures: pre-compression feature data with pid tracking for efficient subsetting."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
|
|
7
|
+
import numpy as np
|
|
8
|
+
from numpy import ndarray
|
|
9
|
+
|
|
10
|
+
from acteval.density.features.utils import compress_feature
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class PidFeatures:
|
|
15
|
+
"""Per-pid feature data that can be subset and aggregated.
|
|
16
|
+
|
|
17
|
+
Stores raw (pre-compression) values alongside pid arrays so that features
|
|
18
|
+
can be efficiently filtered to a subset of persons and then compressed
|
|
19
|
+
into the standard ``{key: (values, weights)}`` format.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
data: ``{key: (values, pids)}`` where *values* is a 1-D (or 2-D)
|
|
23
|
+
array and *pids* is a same-length array of dense pid ints.
|
|
24
|
+
bin_size: Optional bin size forwarded to ``compress_feature``.
|
|
25
|
+
factor: Divisor forwarded to ``compress_feature``.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
data: dict[str, tuple[ndarray, ndarray]]
|
|
29
|
+
bin_size: int | None = None
|
|
30
|
+
factor: int = 1
|
|
31
|
+
|
|
32
|
+
# Internal cache: maps ``id(pid_array)`` → mask for a given pid set,
|
|
33
|
+
# so that per-person features sharing one pid array compute the mask once.
|
|
34
|
+
_mask_cache: dict[int, ndarray] = field(default_factory=dict, repr=False)
|
|
35
|
+
|
|
36
|
+
def subset(self, pid_set: ndarray) -> PidFeatures:
|
|
37
|
+
"""Return a new PidFeatures containing only values for *pid_set*.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
pid_set: 1-D array of dense pid ints to keep.
|
|
41
|
+
"""
|
|
42
|
+
pid_set_unique = np.unique(pid_set)
|
|
43
|
+
new_data: dict[str, tuple[ndarray, ndarray]] = {}
|
|
44
|
+
mask_cache: dict[int, ndarray] = {}
|
|
45
|
+
|
|
46
|
+
for key, (values, pids) in self.data.items():
|
|
47
|
+
pid_id = id(pids)
|
|
48
|
+
if pid_id not in mask_cache:
|
|
49
|
+
mask_cache[pid_id] = np.isin(pids, pid_set_unique)
|
|
50
|
+
mask = mask_cache[pid_id]
|
|
51
|
+
new_data[key] = (values[mask], pids[mask])
|
|
52
|
+
|
|
53
|
+
return PidFeatures(
|
|
54
|
+
data=new_data,
|
|
55
|
+
bin_size=self.bin_size,
|
|
56
|
+
factor=self.factor,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
def aggregate(self) -> dict[str, tuple[ndarray, ndarray]]:
|
|
60
|
+
"""Compress per-pid data into ``{key: (unique_values, counts)}``.
|
|
61
|
+
|
|
62
|
+
Produces identical output to the existing pipeline's
|
|
63
|
+
``weighted_features`` / ``compress_feature`` calls.
|
|
64
|
+
"""
|
|
65
|
+
result: dict[str, tuple[ndarray, ndarray]] = {}
|
|
66
|
+
for key, (values, _pids) in self.data.items():
|
|
67
|
+
if len(values) == 0:
|
|
68
|
+
result[key] = (np.array([]), np.array([], dtype=np.int64))
|
|
69
|
+
else:
|
|
70
|
+
result[key] = compress_feature(
|
|
71
|
+
values, bin_size=self.bin_size, factor=self.factor
|
|
72
|
+
)
|
|
73
|
+
return result
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from numpy import array, ndarray
|
|
3
|
+
|
|
4
|
+
from acteval.density.features.pid_features import PidFeatures
|
|
5
|
+
from acteval.density.features.utils import (
|
|
6
|
+
_collect_by_group,
|
|
7
|
+
_collect_by_group_with_pids,
|
|
8
|
+
weighted_features,
|
|
9
|
+
)
|
|
10
|
+
from acteval.population import Population
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def start_times_by_act(
|
|
14
|
+
population: Population, bin_size: int = 15, factor: int = 1440
|
|
15
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
16
|
+
features = _collect_by_group(population.acts, population.starts)
|
|
17
|
+
return weighted_features(features, bin_size=bin_size, factor=factor)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def end_times_by_act(
|
|
21
|
+
population: Population, bin_size: int = 15, factor: int = 1440
|
|
22
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
23
|
+
features = _collect_by_group(population.acts, population.ends)
|
|
24
|
+
return weighted_features(features, bin_size=bin_size, factor=factor)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def durations_by_act(
|
|
28
|
+
population: Population, bin_size: int = 15, factor: int = 1440
|
|
29
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
30
|
+
features = _collect_by_group(population.acts, population.durations)
|
|
31
|
+
return weighted_features(features, bin_size=bin_size, factor=factor)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def start_durations_by_act(population: Population) -> dict[str, ndarray]:
|
|
35
|
+
if population.is_empty:
|
|
36
|
+
return {a: array([]) for a in population.unique_acts}
|
|
37
|
+
pairs = np.column_stack([population.starts, population.durations])
|
|
38
|
+
return _collect_by_group(population.acts, pairs)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def start_and_duration_by_act_bins(
|
|
42
|
+
population: Population, bin_size: int = 15, factor: int = 1440
|
|
43
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
44
|
+
features = start_durations_by_act(population)
|
|
45
|
+
return weighted_features(features, bin_size=bin_size, factor=factor)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def joint_durations_by_act_bins(
|
|
49
|
+
population: Population, bin_size: int = 15, factor: int = 1440
|
|
50
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
51
|
+
if population.is_empty:
|
|
52
|
+
return {a: array([]) for a in population.unique_acts}
|
|
53
|
+
pids = population.pids
|
|
54
|
+
acts = population.acts.astype(str)
|
|
55
|
+
durations = population.durations.astype(float)
|
|
56
|
+
|
|
57
|
+
# Identify rows that are not the last in their pid group
|
|
58
|
+
same_pid_next = np.empty(len(pids), dtype=bool)
|
|
59
|
+
same_pid_next[:-1] = pids[:-1] == pids[1:]
|
|
60
|
+
same_pid_next[-1] = False
|
|
61
|
+
|
|
62
|
+
# For valid rows, pair current duration with next duration
|
|
63
|
+
valid_acts = acts[same_pid_next]
|
|
64
|
+
valid_durs = durations[same_pid_next]
|
|
65
|
+
next_durs = durations[1:][same_pid_next[:-1]]
|
|
66
|
+
valid_pairs = np.column_stack([valid_durs, next_durs])
|
|
67
|
+
|
|
68
|
+
features = _collect_by_group(valid_acts, valid_pairs)
|
|
69
|
+
return weighted_features(features, bin_size=bin_size, factor=factor)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def start_times_by_act_plan_seq(
|
|
73
|
+
population: Population,
|
|
74
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
75
|
+
features = _collect_by_group(population.seq_key, population.starts)
|
|
76
|
+
return weighted_features(features, factor=1440)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def start_times_by_act_plan_enum(
|
|
80
|
+
population: Population,
|
|
81
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
82
|
+
features = _collect_by_group(population.act_enum_key, population.starts)
|
|
83
|
+
return weighted_features(features, factor=1440)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def end_times_by_act_plan_seq(
|
|
87
|
+
population: Population,
|
|
88
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
89
|
+
features = _collect_by_group(population.seq_key, population.ends)
|
|
90
|
+
return weighted_features(features, factor=1440)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def end_times_by_act_plan_enum(
|
|
94
|
+
population: Population,
|
|
95
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
96
|
+
features = _collect_by_group(population.act_enum_key, population.ends)
|
|
97
|
+
return weighted_features(features, factor=1440)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def durations_by_act_plan_seq(
|
|
101
|
+
population: Population,
|
|
102
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
103
|
+
features = _collect_by_group(population.seq_key, population.durations)
|
|
104
|
+
return weighted_features(features, factor=1440)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def durations_by_act_plan_enum(
|
|
108
|
+
population: Population,
|
|
109
|
+
) -> dict[str, tuple[ndarray, ndarray]]:
|
|
110
|
+
features = _collect_by_group(population.act_enum_key, population.durations)
|
|
111
|
+
return weighted_features(features, factor=1440)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# ---------------------------------------------------------------------------
|
|
115
|
+
# Per-pid variants — return PidFeatures for efficient subsetting
|
|
116
|
+
# ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def start_times_by_act_plan_enum_per_pid(
|
|
120
|
+
population: Population,
|
|
121
|
+
) -> PidFeatures:
|
|
122
|
+
features = _collect_by_group_with_pids(
|
|
123
|
+
population.act_enum_key, population.starts, population.pids
|
|
124
|
+
)
|
|
125
|
+
return PidFeatures(data=features, bin_size=None, factor=1440)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def durations_by_act_plan_enum_per_pid(
|
|
129
|
+
population: Population,
|
|
130
|
+
) -> PidFeatures:
|
|
131
|
+
features = _collect_by_group_with_pids(
|
|
132
|
+
population.act_enum_key, population.durations, population.pids
|
|
133
|
+
)
|
|
134
|
+
return PidFeatures(data=features, bin_size=None, factor=1440)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def start_and_duration_by_act_bins_per_pid(
|
|
138
|
+
population: Population,
|
|
139
|
+
bin_size: int = 15,
|
|
140
|
+
factor: int = 1440,
|
|
141
|
+
) -> PidFeatures:
|
|
142
|
+
if population.is_empty:
|
|
143
|
+
return PidFeatures(data={}, bin_size=bin_size, factor=factor)
|
|
144
|
+
pairs = np.column_stack([population.starts, population.durations])
|
|
145
|
+
features = _collect_by_group_with_pids(population.acts, pairs, population.pids)
|
|
146
|
+
return PidFeatures(data=features, bin_size=bin_size, factor=factor)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def joint_durations_by_act_bins_per_pid(
|
|
150
|
+
population: Population,
|
|
151
|
+
bin_size: int = 15,
|
|
152
|
+
factor: int = 1440,
|
|
153
|
+
) -> PidFeatures:
|
|
154
|
+
if population.is_empty:
|
|
155
|
+
return PidFeatures(data={}, bin_size=bin_size, factor=factor)
|
|
156
|
+
pids = population.pids
|
|
157
|
+
acts = population.acts.astype(str)
|
|
158
|
+
durations = population.durations.astype(float)
|
|
159
|
+
|
|
160
|
+
same_pid_next = np.empty(len(pids), dtype=bool)
|
|
161
|
+
same_pid_next[:-1] = pids[:-1] == pids[1:]
|
|
162
|
+
same_pid_next[-1] = False
|
|
163
|
+
|
|
164
|
+
valid_acts = acts[same_pid_next]
|
|
165
|
+
valid_pids = pids[same_pid_next]
|
|
166
|
+
valid_durs = durations[same_pid_next]
|
|
167
|
+
next_durs = durations[1:][same_pid_next[:-1]]
|
|
168
|
+
valid_pairs = np.column_stack([valid_durs, next_durs])
|
|
169
|
+
|
|
170
|
+
features = _collect_by_group_with_pids(valid_acts, valid_pairs, valid_pids)
|
|
171
|
+
return PidFeatures(data=features, bin_size=bin_size, factor=factor)
|