data-disaggregation 0.10.0__tar.gz → 0.11.0__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.
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/PKG-INFO +15 -6
- data_disaggregation-0.11.0/README.md +13 -0
- data_disaggregation-0.11.0/data_disaggregation/__init__.py +5 -0
- data_disaggregation-0.11.0/data_disaggregation/actions.py +7 -0
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation/base.py +61 -40
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation/ext.py +79 -53
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation/utils.py +48 -30
- data_disaggregation-0.11.0/data_disaggregation/vtypes.py +88 -0
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation.egg-info/PKG-INFO +15 -6
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation.egg-info/SOURCES.txt +2 -1
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/setup.py +1 -1
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/test/test.py +59 -44
- data-disaggregation-0.10.0/README.md +0 -13
- data-disaggregation-0.10.0/data_disaggregation/__init__.py +0 -15
- data-disaggregation-0.10.0/data_disaggregation/classes.py +0 -76
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/LICENSE +0 -0
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation.egg-info/dependency_links.txt +0 -0
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation.egg-info/requires.txt +0 -0
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation.egg-info/top_level.txt +0 -0
- {data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: data-disaggregation
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Author: Christian Winger
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Documentation, https://wingechr.github.io/data-disaggregation
|
|
@@ -11,13 +11,22 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pandas
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: license
|
|
20
|
+
Dynamic: platform
|
|
21
|
+
Dynamic: project-url
|
|
22
|
+
Dynamic: requires-dist
|
|
14
23
|
|
|
15
|
-
#
|
|
24
|
+
# README
|
|
16
25
|
|
|
17
|
-

|
|
18
|
-

|
|
26
|
+
[](./LICENSE)
|
|
27
|
+
[](https://github.com/wingechr/data-disaggregation)
|
|
19
28
|
[](https://pypi.org/project/data-disaggregation/)
|
|
20
|
-

|
|
29
|
+
[](https://github.com/wingechr/data-disaggregation/actions/workflows/test.yml)
|
|
21
30
|
[](https://wingechr.github.io/data-disaggregation/)
|
|
22
31
|
|
|
23
32
|
## Install
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# README
|
|
2
|
+
|
|
3
|
+
[](./LICENSE)
|
|
4
|
+
[](https://github.com/wingechr/data-disaggregation)
|
|
5
|
+
[](https://pypi.org/project/data-disaggregation/)
|
|
6
|
+
[](https://github.com/wingechr/data-disaggregation/actions/workflows/test.yml)
|
|
7
|
+
[](https://wingechr.github.io/data-disaggregation/)
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install data-disaggregation
|
|
13
|
+
```
|
|
@@ -45,10 +45,8 @@ Helper to create the mapping
|
|
|
45
45
|
|
|
46
46
|
"""
|
|
47
47
|
|
|
48
|
-
|
|
49
48
|
from typing import Mapping, Tuple
|
|
50
49
|
|
|
51
|
-
from .classes import VT, F, T, V, VT_NumericExt
|
|
52
50
|
from .utils import (
|
|
53
51
|
as_set,
|
|
54
52
|
group_idx_first,
|
|
@@ -60,42 +58,74 @@ from .utils import (
|
|
|
60
58
|
is_unique,
|
|
61
59
|
iter_values,
|
|
62
60
|
)
|
|
61
|
+
from .vtypes import F, T, V, VariableType, VT_NumericExt
|
|
63
62
|
|
|
64
63
|
VALIDATE_EQ_REL_TOLERANCE = 1e-10
|
|
65
64
|
|
|
66
65
|
|
|
67
66
|
def transform(
|
|
68
|
-
vtype:
|
|
67
|
+
vtype: VariableType,
|
|
69
68
|
data: Mapping[F, V],
|
|
70
69
|
weight_map: Mapping[Tuple[F, T], float],
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
validate=True,
|
|
70
|
+
weights_from: Mapping[F, float] = None,
|
|
71
|
+
weights_to: Mapping[T, float] = None,
|
|
72
|
+
weight_rel_threshold: float = 0.0,
|
|
73
|
+
validate: bool = True,
|
|
75
74
|
) -> Mapping[T, V]:
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
"""(dis-)aggregate data.
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
vtype: VariableType
|
|
80
|
+
Variable type of input data, determines the aggregation method.
|
|
81
|
+
data: Mapping[F, V]
|
|
82
|
+
Input data: mapping (usually dict) from any keys (any hashable) to values.
|
|
83
|
+
weight_map: Mapping[Tuple[F, T], float]
|
|
84
|
+
weights for combinations of input and output elements (must be positive).
|
|
85
|
+
Keys must tuples from input/output key pairs.
|
|
86
|
+
weights_from: Mapping[F, float]
|
|
87
|
+
optional weights of input elements (must be positive).
|
|
88
|
+
If not specified, this will be calculated as a sum from `weight_map`.
|
|
89
|
+
weights_to: Mapping[T, float]
|
|
90
|
+
optional weights of output elements (must be positive).
|
|
91
|
+
If not specified, this will be calculated as a sum from `weight_map`.
|
|
92
|
+
weight_rel_threshold: float
|
|
93
|
+
optional value between 0 and 1: all mappings are dropped
|
|
94
|
+
if the sum of input weights / output weight is smaller than this threshold.
|
|
95
|
+
For example, you may want to set it to 0.5 for geographical mappings with
|
|
96
|
+
extensive data.
|
|
97
|
+
validate bool:
|
|
98
|
+
if True: run additional (but costly) validations of weights and data.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
: Mapping[T, V]
|
|
103
|
+
output data as a mapping from output keys (any hashable) to values.
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
if weights_from is None:
|
|
107
|
+
weights_from = group_idx_first(weight_map)
|
|
108
|
+
|
|
109
|
+
if weights_to is None:
|
|
110
|
+
weights_to = group_idx_second(weight_map)
|
|
81
111
|
|
|
82
112
|
if validate:
|
|
83
113
|
# validate size_f
|
|
84
|
-
assert is_mapping(
|
|
85
|
-
assert is_unique(
|
|
86
|
-
assert all(v > 0 for v in iter_values(
|
|
114
|
+
assert is_mapping(weights_from)
|
|
115
|
+
assert is_unique(weights_from)
|
|
116
|
+
assert all(v > 0 for v in iter_values(weights_from))
|
|
87
117
|
|
|
88
118
|
# validate size_t
|
|
89
|
-
assert is_mapping(
|
|
90
|
-
assert is_unique(
|
|
91
|
-
assert all(v > 0 for v in iter_values(
|
|
119
|
+
assert is_mapping(weights_to)
|
|
120
|
+
assert is_unique(weights_to)
|
|
121
|
+
assert all(v > 0 for v in iter_values(weights_to))
|
|
92
122
|
|
|
93
123
|
# validate var
|
|
94
124
|
assert is_mapping(data)
|
|
95
125
|
assert is_unique(data)
|
|
96
126
|
|
|
97
|
-
if not is_subset(data,
|
|
98
|
-
err = as_set(data) - as_set(
|
|
127
|
+
if not is_subset(data, weights_from):
|
|
128
|
+
err = as_set(data) - as_set(weights_from)
|
|
99
129
|
raise Exception(
|
|
100
130
|
f"Variable index is not a subset of input dimension subset: {err}"
|
|
101
131
|
)
|
|
@@ -104,8 +134,8 @@ def transform(
|
|
|
104
134
|
assert is_map(weight_map)
|
|
105
135
|
assert is_unique(weight_map)
|
|
106
136
|
assert all(v >= 0 for v in iter_values(weight_map))
|
|
107
|
-
assert is_subset([x[0] for x in weight_map.keys()],
|
|
108
|
-
assert is_subset([x[1] for x in weight_map.keys()],
|
|
137
|
+
assert is_subset([x[0] for x in weight_map.keys()], weights_from)
|
|
138
|
+
assert is_subset([x[1] for x in weight_map.keys()], weights_to)
|
|
109
139
|
# assert all(isinstance(v, (float, int)) for v in iter_values(weight_map))
|
|
110
140
|
|
|
111
141
|
# filter nan in data
|
|
@@ -113,22 +143,19 @@ def transform(
|
|
|
113
143
|
|
|
114
144
|
# scale extensive => intensive
|
|
115
145
|
if vtype == VT_NumericExt:
|
|
116
|
-
|
|
117
|
-
data = dict((f, v / size_in[f]) for f, v in data.items())
|
|
118
|
-
except TypeError:
|
|
119
|
-
raise Exception(data)
|
|
146
|
+
data = dict((f, v / weights_from[f]) for f, v in data.items())
|
|
120
147
|
|
|
121
148
|
# filter unused in weight_map: input:
|
|
122
149
|
weight_map = dict(((f, t), w) for (f, t), w in weight_map.items() if f in data)
|
|
123
150
|
|
|
124
151
|
# filter unused in weight_map: output
|
|
125
152
|
weight_map = dict(
|
|
126
|
-
((f, t), w) for (f, t), w in weight_map.items() if
|
|
153
|
+
((f, t), w) for (f, t), w in weight_map.items() if weights_to.get(t, 0) > 0
|
|
127
154
|
)
|
|
128
155
|
|
|
129
156
|
# init groups
|
|
130
157
|
groups = dict((t, []) for t in set(_t for (_, _t) in weight_map.keys()))
|
|
131
|
-
# group data
|
|
158
|
+
# group data by output keys
|
|
132
159
|
for (f, t), w in weight_map.items():
|
|
133
160
|
v = data[f]
|
|
134
161
|
groups[t].append((v, w))
|
|
@@ -136,18 +163,12 @@ def transform(
|
|
|
136
163
|
# create weight sums
|
|
137
164
|
group_sumw = dict((t, sum(w for _, w in vws)) for t, vws in groups.items())
|
|
138
165
|
|
|
139
|
-
if validate or threshold:
|
|
140
|
-
sumw_rel = dict((t, sumw / size_out[t]) for t, sumw in group_sumw.items())
|
|
141
|
-
|
|
142
|
-
# sumw always <= size_out ==> sumw_rel <= 1
|
|
143
|
-
# TODO: maybe drop this check, this should never really happen
|
|
144
|
-
# if validate:
|
|
145
|
-
# scomp = 1 + VALIDATE_EQ_REL_TOLERANCE
|
|
146
|
-
# assert all(s <= scomp for s in sumw_rel.values()), sumw_rel
|
|
147
|
-
|
|
148
166
|
# drop groups under threshold
|
|
149
|
-
if
|
|
150
|
-
|
|
167
|
+
if weight_rel_threshold:
|
|
168
|
+
sumw_rel = dict((t, sumw / weights_to[t]) for t, sumw in group_sumw.items())
|
|
169
|
+
groups = dict(
|
|
170
|
+
(t, vws) for t, vws in groups.items() if sumw_rel[t] >= weight_rel_threshold
|
|
171
|
+
)
|
|
151
172
|
|
|
152
173
|
result = {}
|
|
153
174
|
for t, vws in groups.items():
|
|
@@ -159,6 +180,6 @@ def transform(
|
|
|
159
180
|
# re-scale intensive => extensive
|
|
160
181
|
if vtype == VT_NumericExt:
|
|
161
182
|
for t, v in result.items():
|
|
162
|
-
result[t] = v *
|
|
183
|
+
result[t] = v * weights_to[t]
|
|
163
184
|
|
|
164
185
|
return result
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
"""extended functions, especially for pandas Series
|
|
2
2
|
"""
|
|
3
|
+
|
|
3
4
|
from typing import List, Tuple, Union
|
|
4
5
|
|
|
5
6
|
import numpy as np
|
|
6
7
|
from pandas import DataFrame, Index, MultiIndex, Series
|
|
7
8
|
|
|
8
9
|
from .base import transform
|
|
9
|
-
from .classes import SCALAR_DIM_NAME, SCALAR_INDEX_KEY, VT
|
|
10
10
|
from .utils import is_scalar
|
|
11
|
+
from .vtypes import SCALAR_DIM_NAME, SCALAR_INDEX_KEY, VariableType
|
|
11
12
|
|
|
12
13
|
IDX_SCALAR = MultiIndex.from_product([Index([SCALAR_INDEX_KEY], name=SCALAR_DIM_NAME)])
|
|
13
|
-
|
|
14
|
+
COL_WEIGHT = "__WEIGHT__"
|
|
14
15
|
COL_FROM = "__FROM__"
|
|
15
16
|
COL_TO = "__TO__"
|
|
16
17
|
|
|
@@ -20,31 +21,29 @@ def harmonize_input_data(data: Union[DataFrame, Series, float]) -> DataFrame:
|
|
|
20
21
|
return DataFrame with MultiIndex
|
|
21
22
|
"""
|
|
22
23
|
if is_scalar(data):
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
data = DataFrame({SCALAR_INDEX_KEY: data}, index=IDX_SCALAR)
|
|
25
|
+
elif isinstance(data, Series):
|
|
25
26
|
data = data.to_frame()
|
|
26
27
|
# ensure multiindex
|
|
27
28
|
data = ensure_multiindex(data)
|
|
28
29
|
return data
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
def as_multiindex(
|
|
32
|
-
if not isinstance(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
assert isinstance(item, MultiIndex)
|
|
36
|
-
return item
|
|
32
|
+
def as_multiindex(index: Index) -> MultiIndex:
|
|
33
|
+
if not isinstance(index, MultiIndex):
|
|
34
|
+
index = MultiIndex.from_product([index])
|
|
35
|
+
return index
|
|
37
36
|
|
|
38
37
|
|
|
39
38
|
def ensure_multiindex(item: Union[DataFrame, Series]) -> Union[DataFrame, Series]:
|
|
40
39
|
if not isinstance(item.index, MultiIndex):
|
|
41
|
-
|
|
42
|
-
item
|
|
43
|
-
|
|
40
|
+
index = as_multiindex(item.index)
|
|
41
|
+
item = item.copy() # TODO: can we replace index without copy?
|
|
42
|
+
item.index = index
|
|
44
43
|
return item
|
|
45
44
|
|
|
46
45
|
|
|
47
|
-
def
|
|
46
|
+
def as_list_of_series_w_multiindex(
|
|
48
47
|
items: Union[Index, Series, Tuple[Union[Index, Series]]]
|
|
49
48
|
) -> List[Series]:
|
|
50
49
|
# make sure we have a list/tuple
|
|
@@ -52,14 +51,13 @@ def as_list_of_series(
|
|
|
52
51
|
items = [items]
|
|
53
52
|
# make sure we have series:
|
|
54
53
|
items = [it if isinstance(it, Series) else Series(1, index=it) for it in items]
|
|
55
|
-
for it in items
|
|
56
|
-
valdidate_index(it)
|
|
54
|
+
items = [ensure_multiindex(it) for it in items]
|
|
57
55
|
return items
|
|
58
56
|
|
|
59
57
|
|
|
60
58
|
def merge_indices(items: List[Union[Series, Index]]) -> MultiIndex:
|
|
61
59
|
"""Create product of unions of indices"""
|
|
62
|
-
# ensure items are
|
|
60
|
+
# ensure items are multiindices
|
|
63
61
|
items = [it if isinstance(it, Index) else it.index for it in items]
|
|
64
62
|
items = [as_multiindex(it) for it in items]
|
|
65
63
|
indices = {}
|
|
@@ -83,16 +81,20 @@ def combine_weights(
|
|
|
83
81
|
Series with MultiIndex
|
|
84
82
|
"""
|
|
85
83
|
# make sure we have series:
|
|
86
|
-
weights =
|
|
84
|
+
weights = as_list_of_series_w_multiindex(weights)
|
|
85
|
+
|
|
87
86
|
# merge indices
|
|
88
87
|
idx = merge_indices(weights)
|
|
88
|
+
|
|
89
89
|
# multiply all and drop nan
|
|
90
90
|
result = Series(1, index=idx)
|
|
91
91
|
for w in weights:
|
|
92
|
-
# IMPORTANT: `result *= w` gives a different result,so DONT
|
|
92
|
+
# IMPORTANT: `result *= w` gives a different result, so DONT use it
|
|
93
93
|
result = result * w
|
|
94
|
+
|
|
94
95
|
# multiplications implicit join can change order:
|
|
95
|
-
result.index
|
|
96
|
+
if isinstance(result.index, MultiIndex):
|
|
97
|
+
result.index = result.index.reorder_levels(idx.names)
|
|
96
98
|
|
|
97
99
|
# drop nan
|
|
98
100
|
result.dropna(inplace=True)
|
|
@@ -142,6 +144,7 @@ def remap_series_to_frame(s: Series, idx: MultiIndex, colname: str) -> DataFrame
|
|
|
142
144
|
# convert index into columns
|
|
143
145
|
df_result.reset_index(inplace=True)
|
|
144
146
|
# set index to same levels as `s`, but keep columns
|
|
147
|
+
|
|
145
148
|
df_result.set_index(s.index.names, drop=False, inplace=True)
|
|
146
149
|
df_result = ensure_multiindex(df_result)
|
|
147
150
|
|
|
@@ -163,7 +166,7 @@ def create_weight_map(
|
|
|
163
166
|
"""
|
|
164
167
|
idx_all = merge_indices([idx_in, idx_out])
|
|
165
168
|
# expand index (TODO: check if weights are dropped??)
|
|
166
|
-
df = remap_series_to_frame(ds_weights, idx_all,
|
|
169
|
+
df = remap_series_to_frame(ds_weights, idx_all, COL_WEIGHT)
|
|
167
170
|
df[COL_FROM] = list(zip(*[df[n] for n in idx_in.names]))
|
|
168
171
|
df[COL_TO] = list(zip(*[df[n] for n in idx_out.names]))
|
|
169
172
|
|
|
@@ -171,34 +174,64 @@ def create_weight_map(
|
|
|
171
174
|
df = df.loc[df[COL_FROM].isin(idx_in)]
|
|
172
175
|
df = df.loc[df[COL_TO].isin(idx_out)]
|
|
173
176
|
|
|
174
|
-
ds_weight_map = df.set_index([COL_FROM, COL_TO])[
|
|
177
|
+
ds_weight_map = df.set_index([COL_FROM, COL_TO])[COL_WEIGHT]
|
|
175
178
|
ds_weight_map = ds_weight_map.fillna(0)
|
|
176
179
|
|
|
177
180
|
return ds_weight_map
|
|
178
181
|
|
|
179
182
|
|
|
180
|
-
def
|
|
183
|
+
def validate_multiindex(item: Union[Index, Series, DataFrame]):
|
|
181
184
|
if isinstance(item, (Series, DataFrame)):
|
|
182
185
|
item = item.index
|
|
183
|
-
|
|
184
|
-
assert
|
|
185
|
-
assert item.
|
|
186
|
+
assert isinstance(item, MultiIndex)
|
|
187
|
+
assert all(item.names) # no empty names
|
|
188
|
+
assert len(set(item.names)) == len(item.names) # unique names
|
|
189
|
+
assert item.is_unique # unique values
|
|
186
190
|
|
|
187
191
|
|
|
188
192
|
def transform_pandas(
|
|
189
|
-
vtype:
|
|
193
|
+
vtype: VariableType,
|
|
190
194
|
data: Union[DataFrame, Series, float],
|
|
191
195
|
weights: Union[Index, Series, Tuple[Union[Index, Series]]],
|
|
192
196
|
dim_in: Union[Index, Series] = None,
|
|
193
197
|
dim_out: Union[Index, Series] = None,
|
|
198
|
+
validate: bool = True,
|
|
194
199
|
) -> Union[DataFrame, Series, float]:
|
|
200
|
+
"""(dis-)aggregate data (pandas).
|
|
201
|
+
|
|
202
|
+
Parameters
|
|
203
|
+
----------
|
|
204
|
+
vtype : VariableType
|
|
205
|
+
Variable type of input data, determines the aggregation method.
|
|
206
|
+
data : Union[DataFrame, Series, float]
|
|
207
|
+
Input data. Indices must be unique and have unique level names
|
|
208
|
+
weights : Union[Index, Series, Tuple[Union[Index, Series]]]
|
|
209
|
+
weights for combinations of input and output elements (must be positive).
|
|
210
|
+
dim_in : Union[Index, Series], optional
|
|
211
|
+
TODO
|
|
212
|
+
dim_out : Union[Index, Series], optional
|
|
213
|
+
TODO
|
|
214
|
+
validate : bool, optional
|
|
215
|
+
if True: run additional (but costly) validations of weights and data.
|
|
216
|
+
|
|
217
|
+
Returns
|
|
218
|
+
-------
|
|
219
|
+
Union[DataFrame, Series, float]
|
|
220
|
+
output data
|
|
221
|
+
|
|
222
|
+
"""
|
|
223
|
+
|
|
195
224
|
# ensure data is DataFrame with MultiIndex
|
|
196
225
|
df_data = harmonize_input_data(data)
|
|
197
|
-
|
|
226
|
+
|
|
227
|
+
if validate:
|
|
228
|
+
validate_multiindex(df_data)
|
|
198
229
|
|
|
199
230
|
# combine weights into single Series with MultiIndex
|
|
200
231
|
ds_weights = combine_weights(weights)
|
|
201
|
-
|
|
232
|
+
|
|
233
|
+
if validate:
|
|
234
|
+
validate_multiindex(ds_weights)
|
|
202
235
|
|
|
203
236
|
# determine input index
|
|
204
237
|
if dim_in is None:
|
|
@@ -222,16 +255,17 @@ def transform_pandas(
|
|
|
222
255
|
ds_size_out = ensure_multiindex(dim_out)
|
|
223
256
|
idx_out = ds_size_out.index
|
|
224
257
|
|
|
225
|
-
|
|
226
|
-
|
|
258
|
+
if validate:
|
|
259
|
+
validate_multiindex(idx_in)
|
|
260
|
+
validate_multiindex(idx_out)
|
|
227
261
|
|
|
228
262
|
# create weight map
|
|
229
263
|
ds_weight_map = create_weight_map(ds_weights, idx_in, idx_out)
|
|
230
264
|
|
|
231
|
-
valdidate_index(ds_weight_map)
|
|
232
|
-
|
|
233
265
|
if ds_size_in is None:
|
|
234
|
-
ds_size_in =
|
|
266
|
+
ds_size_in = (
|
|
267
|
+
ds_weight_map.reset_index().groupby(COL_FROM).sum(COL_WEIGHT)[COL_WEIGHT]
|
|
268
|
+
)
|
|
235
269
|
# fix index
|
|
236
270
|
ds_size_in.index = MultiIndex.from_tuples(
|
|
237
271
|
ds_size_in.index.values, names=idx_in.names
|
|
@@ -239,28 +273,20 @@ def transform_pandas(
|
|
|
239
273
|
ds_size_in = ds_size_in.loc[ds_size_in > 0]
|
|
240
274
|
|
|
241
275
|
if ds_size_out is None:
|
|
242
|
-
ds_size_out =
|
|
276
|
+
ds_size_out = (
|
|
277
|
+
ds_weight_map.reset_index().groupby(COL_TO).sum(COL_WEIGHT)[COL_WEIGHT]
|
|
278
|
+
)
|
|
243
279
|
# fix index
|
|
244
280
|
ds_size_out.index = MultiIndex.from_tuples(
|
|
245
281
|
ds_size_out.index.values, names=idx_out.names
|
|
246
282
|
)
|
|
247
283
|
ds_size_out = ds_size_out.loc[ds_size_out > 0]
|
|
248
284
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
# TODO drop from ds_weight_map
|
|
256
|
-
# TODO: drop from data
|
|
257
|
-
|
|
258
|
-
# df_data = df_data[idx_in]
|
|
259
|
-
|
|
260
|
-
# raise Exception(idx_in[idx_in.isin(ds_size_in.index)])
|
|
261
|
-
|
|
262
|
-
# assert ds_size_in.index.equals(idx_in), (ds_size_in.index, idx_in)
|
|
263
|
-
# assert ds_size_out.index.equals(idx_out), (ds_size_out.index, idx_out)
|
|
285
|
+
if validate:
|
|
286
|
+
validate_multiindex(ds_size_in)
|
|
287
|
+
validate_multiindex(ds_size_out)
|
|
288
|
+
assert ds_size_in.index.isin(idx_in).all()
|
|
289
|
+
assert ds_size_out.index.isin(idx_out).all()
|
|
264
290
|
|
|
265
291
|
idx_in = ds_size_in.index
|
|
266
292
|
idx_out = ds_size_out.index
|
|
@@ -275,9 +301,9 @@ def transform_pandas(
|
|
|
275
301
|
vtype=vtype,
|
|
276
302
|
data=s_col,
|
|
277
303
|
weight_map=ds_weight_map,
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
304
|
+
weights_from=ds_size_in,
|
|
305
|
+
weights_to=ds_size_out,
|
|
306
|
+
weight_rel_threshold=0.0,
|
|
281
307
|
validate=False, # we do validation in pandas
|
|
282
308
|
)
|
|
283
309
|
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
"""utility functions
|
|
2
2
|
"""
|
|
3
|
+
|
|
3
4
|
import math
|
|
4
5
|
from typing import Callable, List, Mapping, Tuple
|
|
5
6
|
|
|
6
7
|
from pandas import DataFrame, Index, Series
|
|
7
8
|
|
|
8
|
-
from . import
|
|
9
|
+
from . import vtypes
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
def group_sum(key_vals: Mapping, get_key: Callable = None) -> Mapping:
|
|
12
|
-
"""simple group sum
|
|
13
|
+
"""simple group sum.
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
Parameters
|
|
16
|
+
----------
|
|
17
|
+
key_vals: Mapping
|
|
18
|
+
* keys can be anything hashable,
|
|
19
|
+
* values must be numerical
|
|
20
|
+
get_key: Callable = None
|
|
18
21
|
|
|
19
22
|
Returns
|
|
23
|
+
-------
|
|
24
|
+
: Mapping
|
|
20
25
|
list of (unique key, sum of values) pairs
|
|
21
26
|
"""
|
|
22
27
|
|
|
@@ -34,15 +39,19 @@ def group_sum(key_vals: Mapping, get_key: Callable = None) -> Mapping:
|
|
|
34
39
|
|
|
35
40
|
|
|
36
41
|
def weighted_sum(value_normweights: Tuple[float]) -> float:
|
|
37
|
-
"""get sum product
|
|
42
|
+
"""get sum product.
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
value_normweights : list
|
|
47
|
+
non empty list of (value, weight) pairs.
|
|
48
|
+
* values must be numerical.
|
|
49
|
+
* weights must be numerical, positive, and sum up to 1.0.
|
|
43
50
|
|
|
44
51
|
Returns
|
|
45
|
-
|
|
52
|
+
-------
|
|
53
|
+
: float
|
|
54
|
+
|
|
46
55
|
"""
|
|
47
56
|
# TODO faster methods with numpy or ?
|
|
48
57
|
return sum(v * w for v, w in value_normweights)
|
|
@@ -51,13 +60,16 @@ def weighted_sum(value_normweights: Tuple[float]) -> float:
|
|
|
51
60
|
def weighted_mode(value_normweights: Tuple):
|
|
52
61
|
"""get most common value (but by weight)
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
value_normweights : list
|
|
66
|
+
non empty list of (value, weight) pairs.
|
|
67
|
+
* values must be anything sortable.
|
|
68
|
+
* weights must be numerical, positive, and sum up to 1.0.
|
|
58
69
|
|
|
59
70
|
Returns
|
|
60
|
-
|
|
71
|
+
-------
|
|
72
|
+
Any
|
|
61
73
|
"""
|
|
62
74
|
# make values unique (sum weights)
|
|
63
75
|
value_normweights = group_sum(value_normweights).items()
|
|
@@ -68,13 +80,16 @@ def weighted_mode(value_normweights: Tuple):
|
|
|
68
80
|
def weighted_percentile(value_normweights: Tuple, p=0.5):
|
|
69
81
|
"""get most median (but by weight)
|
|
70
82
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
value_normweights : list
|
|
86
|
+
non empty list of (value, weight) pairs.
|
|
87
|
+
* values must be anything sortable.
|
|
88
|
+
* weights must be numerical, positive, and sum up to 1.0.
|
|
75
89
|
|
|
76
90
|
Returns
|
|
77
|
-
|
|
91
|
+
-------
|
|
92
|
+
Any
|
|
78
93
|
"""
|
|
79
94
|
# make values unique (sum weights)
|
|
80
95
|
value_normweights = group_sum(value_normweights).items()
|
|
@@ -90,13 +105,16 @@ def weighted_percentile(value_normweights: Tuple, p=0.5):
|
|
|
90
105
|
def weighted_median(value_normweights: Tuple):
|
|
91
106
|
"""get most median (but by weight)
|
|
92
107
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
108
|
+
Parameters
|
|
109
|
+
----------
|
|
110
|
+
value_normweights : list
|
|
111
|
+
non empty list of (value, weight) pairs.
|
|
112
|
+
* values must be anything sortable.
|
|
113
|
+
* weights must be numerical, positive, and sum up to 1.0.
|
|
97
114
|
|
|
98
115
|
Returns
|
|
99
|
-
|
|
116
|
+
-------
|
|
117
|
+
Any
|
|
100
118
|
"""
|
|
101
119
|
return weighted_percentile(value_normweights, p=0.5)
|
|
102
120
|
|
|
@@ -160,7 +178,7 @@ def as_mapping(x, default_val=1) -> Mapping:
|
|
|
160
178
|
elif is_list(x):
|
|
161
179
|
return dict((k, default_val) for k in x)
|
|
162
180
|
elif is_scalar(x):
|
|
163
|
-
return {
|
|
181
|
+
return {vtypes.SCALAR_INDEX_KEY: x}
|
|
164
182
|
raise TypeError(x)
|
|
165
183
|
|
|
166
184
|
|
|
@@ -168,8 +186,8 @@ def as_scalar(x):
|
|
|
168
186
|
if as_scalar(x):
|
|
169
187
|
return x
|
|
170
188
|
elif is_mapping(x):
|
|
171
|
-
assert set(x.keys()) == set([
|
|
172
|
-
return x[
|
|
189
|
+
assert set(x.keys()) == set([vtypes.SCALAR_INDEX_KEY])
|
|
190
|
+
return x[vtypes.SCALAR_INDEX_KEY]
|
|
173
191
|
raise TypeError(x)
|
|
174
192
|
|
|
175
193
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Type classes for data.
|
|
2
|
+
"""
|
|
3
|
+
|
|
4
|
+
from abc import ABC
|
|
5
|
+
from typing import TypeVar
|
|
6
|
+
|
|
7
|
+
from . import utils
|
|
8
|
+
|
|
9
|
+
F = TypeVar("F")
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
V = TypeVar("V")
|
|
12
|
+
|
|
13
|
+
SCALAR_DIM_NAME = "__SCALAR__"
|
|
14
|
+
# TODO: using None in pandas causes problems with autoconvert to nan
|
|
15
|
+
SCALAR_INDEX_KEY = "__SCALAR__"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class VariableType(ABC):
|
|
19
|
+
@classmethod
|
|
20
|
+
def weighted_aggregate(cls, data):
|
|
21
|
+
"""aggregate data
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
data : Iterable
|
|
26
|
+
non empty list of (value, weight) pairs.
|
|
27
|
+
weights must be numerical, positive, and sum up to 1.0.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
aggregated value
|
|
32
|
+
"""
|
|
33
|
+
raise NotImplementedError()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class VT_Nominal(VariableType):
|
|
37
|
+
"""Type class for nominal (categorical) data.
|
|
38
|
+
|
|
39
|
+
- Aggregation method: mode (most commonly used)
|
|
40
|
+
- Disaggregation method: keep value
|
|
41
|
+
- Examples: regional codes
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def weighted_aggregate(cls, data):
|
|
46
|
+
return utils.weighted_mode(data)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class VT_Ordinal(VT_Nominal):
|
|
50
|
+
"""Type class for ordinal data (ranked categorical).
|
|
51
|
+
|
|
52
|
+
- Aggregation method: median
|
|
53
|
+
- Disaggregation method: keep value
|
|
54
|
+
- Examples: Level of agreement
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def weighted_aggregate(cls, data):
|
|
59
|
+
return utils.weighted_median(data)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class VT_Numeric(VariableType):
|
|
63
|
+
"""Type class for numerical, intensive data
|
|
64
|
+
|
|
65
|
+
An intensive variable is one which does not scale with the system size.
|
|
66
|
+
|
|
67
|
+
- Aggregation method: weighted average
|
|
68
|
+
- Disaggregation method: keep value
|
|
69
|
+
- Examples: temperature, density, pressure
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def weighted_aggregate(cls, data):
|
|
74
|
+
return utils.weighted_sum(data)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class VT_NumericExt(VT_Numeric):
|
|
78
|
+
"""Type class for numerical, extensive data.
|
|
79
|
+
|
|
80
|
+
An extensive variable is one which does scale with the system size
|
|
81
|
+
(assuming an equal distribution).
|
|
82
|
+
|
|
83
|
+
- Aggregation method: sum
|
|
84
|
+
- Disaggregation method: distribute by weights
|
|
85
|
+
- Examples: population, energy, total cost
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
pass
|
{data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.2
|
|
2
2
|
Name: data-disaggregation
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Author: Christian Winger
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Documentation, https://wingechr.github.io/data-disaggregation
|
|
@@ -11,13 +11,22 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
11
11
|
Classifier: Operating System :: OS Independent
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
13
|
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pandas
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: license
|
|
20
|
+
Dynamic: platform
|
|
21
|
+
Dynamic: project-url
|
|
22
|
+
Dynamic: requires-dist
|
|
14
23
|
|
|
15
|
-
#
|
|
24
|
+
# README
|
|
16
25
|
|
|
17
|
-

|
|
18
|
-

|
|
26
|
+
[](./LICENSE)
|
|
27
|
+
[](https://github.com/wingechr/data-disaggregation)
|
|
19
28
|
[](https://pypi.org/project/data-disaggregation/)
|
|
20
|
-

|
|
29
|
+
[](https://github.com/wingechr/data-disaggregation/actions/workflows/test.yml)
|
|
21
30
|
[](https://wingechr.github.io/data-disaggregation/)
|
|
22
31
|
|
|
23
32
|
## Install
|
{data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation.egg-info/SOURCES.txt
RENAMED
|
@@ -2,10 +2,11 @@ LICENSE
|
|
|
2
2
|
README.md
|
|
3
3
|
setup.py
|
|
4
4
|
data_disaggregation/__init__.py
|
|
5
|
+
data_disaggregation/actions.py
|
|
5
6
|
data_disaggregation/base.py
|
|
6
|
-
data_disaggregation/classes.py
|
|
7
7
|
data_disaggregation/ext.py
|
|
8
8
|
data_disaggregation/utils.py
|
|
9
|
+
data_disaggregation/vtypes.py
|
|
9
10
|
data_disaggregation.egg-info/PKG-INFO
|
|
10
11
|
data_disaggregation.egg-info/SOURCES.txt
|
|
11
12
|
data_disaggregation.egg-info/dependency_links.txt
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import doctest
|
|
1
2
|
import logging
|
|
2
3
|
from functools import partial
|
|
3
4
|
from unittest import TestCase
|
|
@@ -6,18 +7,13 @@ import numpy as np
|
|
|
6
7
|
import pandas as pd
|
|
7
8
|
from pandas import DataFrame, Index, MultiIndex, Series
|
|
8
9
|
|
|
10
|
+
from data_disaggregation import ext
|
|
9
11
|
from data_disaggregation.base import transform
|
|
10
|
-
from data_disaggregation.classes import (
|
|
11
|
-
SCALAR_INDEX_KEY,
|
|
12
|
-
VT_Nominal,
|
|
13
|
-
VT_Numeric,
|
|
14
|
-
VT_NumericExt,
|
|
15
|
-
VT_Ordinal,
|
|
16
|
-
)
|
|
17
12
|
from data_disaggregation.ext import (
|
|
18
13
|
COL_FROM,
|
|
19
14
|
COL_TO,
|
|
20
|
-
|
|
15
|
+
COL_WEIGHT,
|
|
16
|
+
as_multiindex,
|
|
21
17
|
combine_weights,
|
|
22
18
|
create_weight_map,
|
|
23
19
|
ensure_multiindex,
|
|
@@ -37,6 +33,13 @@ from data_disaggregation.utils import (
|
|
|
37
33
|
weighted_mode,
|
|
38
34
|
weighted_sum,
|
|
39
35
|
)
|
|
36
|
+
from data_disaggregation.vtypes import (
|
|
37
|
+
SCALAR_INDEX_KEY,
|
|
38
|
+
VT_Nominal,
|
|
39
|
+
VT_Numeric,
|
|
40
|
+
VT_NumericExt,
|
|
41
|
+
VT_Ordinal,
|
|
42
|
+
)
|
|
40
43
|
|
|
41
44
|
logging.basicConfig(
|
|
42
45
|
format="[%(asctime)s %(levelname)7s] %(message)s",
|
|
@@ -78,7 +81,6 @@ class TestUtils(TestCase):
|
|
|
78
81
|
(float("inf"), True),
|
|
79
82
|
(float("nan"), True),
|
|
80
83
|
(np.nan, True),
|
|
81
|
-
(np.NAN, True),
|
|
82
84
|
]:
|
|
83
85
|
self.assertEqual(is_na(x), y)
|
|
84
86
|
|
|
@@ -124,8 +126,6 @@ class TestUtils(TestCase):
|
|
|
124
126
|
|
|
125
127
|
|
|
126
128
|
class TestBase(TestCase):
|
|
127
|
-
""""""
|
|
128
|
-
|
|
129
129
|
def get_example(self, vtype):
|
|
130
130
|
"""
|
|
131
131
|
M | D E F | S | V
|
|
@@ -255,7 +255,7 @@ class TestBaseExamples(TestCase):
|
|
|
255
255
|
(2, SCALAR_INDEX_KEY): 20,
|
|
256
256
|
(3, SCALAR_INDEX_KEY): 30,
|
|
257
257
|
},
|
|
258
|
-
|
|
258
|
+
weight_rel_threshold=0.5,
|
|
259
259
|
)
|
|
260
260
|
self.assertAlmostEqual(res[SCALAR_INDEX_KEY], 1 + 1 + 10)
|
|
261
261
|
|
|
@@ -285,9 +285,9 @@ class TestBaseExamples(TestCase):
|
|
|
285
285
|
(SCALAR_INDEX_KEY, "11"): 0.99, # cell will be dropped (size = 2)
|
|
286
286
|
(SCALAR_INDEX_KEY, "10"): 1.1, # (size = 2)
|
|
287
287
|
},
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
weights_from={SCALAR_INDEX_KEY: 5},
|
|
289
|
+
weights_to={"00": 1, "01": 1, "11": 2, "10": 2},
|
|
290
|
+
weight_rel_threshold=0.5,
|
|
291
291
|
)
|
|
292
292
|
|
|
293
293
|
self.assertAlmostEqual(res["00"], 100 / 5)
|
|
@@ -337,6 +337,13 @@ class TestBaseExamples(TestCase):
|
|
|
337
337
|
|
|
338
338
|
class TextExtPandas(TestCase):
|
|
339
339
|
def assertPandasEqal(self, left, right):
|
|
340
|
+
if isinstance(left, Index):
|
|
341
|
+
left = left.sort_values()
|
|
342
|
+
right = right.sort_values()
|
|
343
|
+
else:
|
|
344
|
+
left = left.sort_index()
|
|
345
|
+
right = right.sort_index()
|
|
346
|
+
|
|
340
347
|
if isinstance(left, Index):
|
|
341
348
|
method = pd.testing.assert_index_equal
|
|
342
349
|
elif isinstance(left, Series):
|
|
@@ -398,20 +405,19 @@ class TextExtPandas(TestCase):
|
|
|
398
405
|
|
|
399
406
|
def test_combine_weights_1(self):
|
|
400
407
|
# no overlap
|
|
401
|
-
s_res = Series(
|
|
402
|
-
[2, 3, 2, 3],
|
|
403
|
-
index=MultiIndex.from_tuples(
|
|
404
|
-
[(11, 21), (11, 22), (12, 21), (12, 22)], names=["i1", "i2"]
|
|
405
|
-
),
|
|
406
|
-
)
|
|
407
408
|
self.assertPandasEqal(
|
|
408
|
-
s_res,
|
|
409
409
|
combine_weights(
|
|
410
410
|
[
|
|
411
411
|
Index([11, 12], name="i1"),
|
|
412
412
|
Series([2, 3], index=Index([21, 22], name="i2")),
|
|
413
413
|
]
|
|
414
414
|
),
|
|
415
|
+
Series(
|
|
416
|
+
[2, 3, 2, 3],
|
|
417
|
+
index=MultiIndex.from_tuples(
|
|
418
|
+
[(11, 21), (11, 22), (12, 21), (12, 22)], names=["i1", "i2"]
|
|
419
|
+
),
|
|
420
|
+
),
|
|
415
421
|
)
|
|
416
422
|
|
|
417
423
|
def test_combine_weights_2(self):
|
|
@@ -459,7 +465,7 @@ class TextExtPandas(TestCase):
|
|
|
459
465
|
],
|
|
460
466
|
names=[COL_FROM, COL_TO],
|
|
461
467
|
),
|
|
462
|
-
name=
|
|
468
|
+
name=COL_WEIGHT,
|
|
463
469
|
)
|
|
464
470
|
|
|
465
471
|
self.assertPandasEqal(ds_result, create_weight_map(ds_weights, idx_in, idx_out))
|
|
@@ -477,27 +483,36 @@ class TextExtPandas(TestCase):
|
|
|
477
483
|
ensure_multiindex(DataFrame({"v": [1, 1]}, index=Index([1, 1], name="i1"))),
|
|
478
484
|
)
|
|
479
485
|
|
|
480
|
-
def
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
[1, 2, 1
|
|
491
|
-
|
|
492
|
-
[
|
|
486
|
+
def test_pandas_utils(self):
|
|
487
|
+
for idx in [Index(["b", "a"], name="d1")]:
|
|
488
|
+
idx2 = as_multiindex(idx)
|
|
489
|
+
self.assertEqual(idx.names, idx2.names)
|
|
490
|
+
self.assertEqual(tuple(tuple(x) for x in idx.values), tuple(idx2.values))
|
|
491
|
+
|
|
492
|
+
def test_transform_pandas(self):
|
|
493
|
+
self.assertPandasEqal(
|
|
494
|
+
transform_pandas(
|
|
495
|
+
vtype=VT_NumericExt,
|
|
496
|
+
data=Series([1, 2], index=Index([0, 1], name="i1"), name="s1"),
|
|
497
|
+
weights=Series(
|
|
498
|
+
[1, 2, 1.5],
|
|
499
|
+
index=MultiIndex.from_tuples(
|
|
500
|
+
[(0, "a"), (0, "b"), (1, "c")], names=["i1", "i2"]
|
|
501
|
+
),
|
|
493
502
|
),
|
|
494
503
|
),
|
|
504
|
+
Series(
|
|
505
|
+
[1 / (2 + 1), 2 / (2 + 1), 2.0],
|
|
506
|
+
index=MultiIndex.from_tuples([("a",), ("b",), ("c",)], names=["i2"]),
|
|
507
|
+
name="s1",
|
|
508
|
+
),
|
|
495
509
|
)
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
class Doctests(TestCase):
|
|
513
|
+
def run_doctest(self, module):
|
|
514
|
+
report = doctest.testmod(module)
|
|
515
|
+
self.assertFalse(report.failed)
|
|
516
|
+
|
|
517
|
+
def test_modules(self):
|
|
518
|
+
self.run_doctest(ext)
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# DataDisAggregation
|
|
2
|
-
|
|
3
|
-

|
|
4
|
-

|
|
5
|
-
[](https://pypi.org/project/data-disaggregation/)
|
|
6
|
-

|
|
7
|
-
[](https://wingechr.github.io/data-disaggregation/)
|
|
8
|
-
|
|
9
|
-
## Install
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
pip install data-disaggregation
|
|
13
|
-
```
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
__version__ = "0.10.0"
|
|
2
|
-
|
|
3
|
-
# isort: skip_file -> keep order to prevent circular import
|
|
4
|
-
from .ext import transform_pandas
|
|
5
|
-
from .base import transform
|
|
6
|
-
from .classes import VT_Nominal, VT_Numeric, VT_NumericExt, VT_Ordinal
|
|
7
|
-
|
|
8
|
-
__all__ = [
|
|
9
|
-
"VT_Nominal",
|
|
10
|
-
"VT_Ordinal",
|
|
11
|
-
"VT_Numeric",
|
|
12
|
-
"VT_NumericExt",
|
|
13
|
-
"transform_pandas",
|
|
14
|
-
"transform",
|
|
15
|
-
]
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"""classes and types
|
|
2
|
-
"""
|
|
3
|
-
|
|
4
|
-
from abc import ABC
|
|
5
|
-
from typing import TypeVar
|
|
6
|
-
|
|
7
|
-
from . import utils
|
|
8
|
-
|
|
9
|
-
F = TypeVar("F")
|
|
10
|
-
T = TypeVar("T")
|
|
11
|
-
V = TypeVar("V")
|
|
12
|
-
|
|
13
|
-
SCALAR_DIM_NAME = ""
|
|
14
|
-
# TODO: using None in pandas causes problems with autoconvert to nan
|
|
15
|
-
SCALAR_INDEX_KEY = ""
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class VT(ABC):
|
|
19
|
-
@classmethod
|
|
20
|
-
def weighted_aggregate(cls, data):
|
|
21
|
-
"""aggregation
|
|
22
|
-
|
|
23
|
-
Args:
|
|
24
|
-
data (list): non empty list of (value, weight) pairs
|
|
25
|
-
|
|
26
|
-
Returns
|
|
27
|
-
aggregated value
|
|
28
|
-
"""
|
|
29
|
-
raise NotImplementedError()
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
class VT_Nominal(VT):
|
|
33
|
-
"""
|
|
34
|
-
Examples: Regional Codes
|
|
35
|
-
"""
|
|
36
|
-
|
|
37
|
-
@classmethod
|
|
38
|
-
def weighted_aggregate(cls, data):
|
|
39
|
-
return utils.weighted_mode(data)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
class VT_Ordinal(VT_Nominal):
|
|
43
|
-
"""
|
|
44
|
-
Values can be sorted in a meaningful way
|
|
45
|
-
Usually, that means using numerical codes that
|
|
46
|
-
do not represent a metric distance, liek a likert scale
|
|
47
|
-
|
|
48
|
-
Examples: [1 = "a little", 2 = "somewhat", 3 = "a lot"]
|
|
49
|
-
|
|
50
|
-
"""
|
|
51
|
-
|
|
52
|
-
@classmethod
|
|
53
|
-
def weighted_aggregate(cls, data):
|
|
54
|
-
return utils.weighted_median(data)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
class VT_Numeric(VT):
|
|
58
|
-
"""
|
|
59
|
-
* Values can be calculated by linear combinations
|
|
60
|
-
* Examples: height, temperature, density
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
-
@classmethod
|
|
64
|
-
def weighted_aggregate(cls, data):
|
|
65
|
-
return utils.weighted_sum(data)
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
class VT_NumericExt(VT_Numeric):
|
|
69
|
-
"""
|
|
70
|
-
Values are extensive, i.e. they are can be transformed into intensive
|
|
71
|
-
by dividing by domain size
|
|
72
|
-
|
|
73
|
-
* Examples: population, energy production
|
|
74
|
-
"""
|
|
75
|
-
|
|
76
|
-
pass
|
|
File without changes
|
|
File without changes
|
{data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation.egg-info/requires.txt
RENAMED
|
File without changes
|
{data-disaggregation-0.10.0 → data_disaggregation-0.11.0}/data_disaggregation.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|