dukit 0.2.6__tar.gz → 0.2.7__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.
- {dukit-0.2.6 → dukit-0.2.7}/PKG-INFO +3 -3
- {dukit-0.2.6 → dukit-0.2.7}/README.md +2 -2
- {dukit-0.2.6 → dukit-0.2.7}/pyproject.toml +1 -1
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/__init__.py +18 -10
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/diffing.py +14 -3
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/interfaces.py +123 -89
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/pandas.py +555 -157
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/qlang/symbols.py +2 -2
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/_test_utils.py +0 -0
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/excel.py +0 -0
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/os.py +0 -0
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/py.typed +0 -0
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/qlang/__init__.py +0 -0
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/qlang/engine.py +0 -0
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/typing.py +0 -0
- {dukit-0.2.6 → dukit-0.2.7}/src/dukit/utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: dukit
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
4
4
|
Summary: dukit - data utilities kit for exploring, modifying and visualizing small but messy data using pandas
|
|
5
5
|
Keywords: pandas,query,utility,data,diff
|
|
6
6
|
Author: MartinVoelkl94
|
|
@@ -89,8 +89,8 @@ these functions offer various ways to deal with non-unique keys/ids.
|
|
|
89
89
|
primarily used when merging 2 dfs with a one-to-many relationship.
|
|
90
90
|
|
|
91
91
|
available functions:
|
|
92
|
-
- df.dk.
|
|
93
|
-
- df.dk.
|
|
92
|
+
- df.dk.stack()
|
|
93
|
+
- df.dk.unstack()
|
|
94
94
|
- df.dk.embed()
|
|
95
95
|
- df.dk.collapse()
|
|
96
96
|
<br>
|
|
@@ -72,8 +72,8 @@ these functions offer various ways to deal with non-unique keys/ids.
|
|
|
72
72
|
primarily used when merging 2 dfs with a one-to-many relationship.
|
|
73
73
|
|
|
74
74
|
available functions:
|
|
75
|
-
- df.dk.
|
|
76
|
-
- df.dk.
|
|
75
|
+
- df.dk.stack()
|
|
76
|
+
- df.dk.unstack()
|
|
77
77
|
- df.dk.embed()
|
|
78
78
|
- df.dk.collapse()
|
|
79
79
|
<br>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "dukit"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.7"
|
|
4
4
|
description = "dukit - data utilities kit for exploring, modifying and visualizing small but messy data using pandas"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{ name = "MartinVoelkl94", email = "martinv16248@gmail.com" }]
|
|
@@ -24,20 +24,24 @@ from .excel import (
|
|
|
24
24
|
save,
|
|
25
25
|
)
|
|
26
26
|
from .pandas import (
|
|
27
|
-
|
|
27
|
+
compose,
|
|
28
|
+
decompose,
|
|
29
|
+
stack,
|
|
30
|
+
unstack,
|
|
31
|
+
transpose,
|
|
28
32
|
date_delta,
|
|
29
33
|
date_table,
|
|
30
34
|
deduplicate,
|
|
31
|
-
embed,
|
|
32
|
-
flatten,
|
|
33
35
|
get_df,
|
|
34
36
|
get_df_patients,
|
|
35
37
|
get_df_medications,
|
|
36
38
|
get_df_old,
|
|
37
39
|
get_df_new,
|
|
38
|
-
stagger,
|
|
39
|
-
transpose,
|
|
40
40
|
split_col,
|
|
41
|
+
embed,
|
|
42
|
+
collapse,
|
|
43
|
+
stagger,
|
|
44
|
+
flatten,
|
|
41
45
|
)
|
|
42
46
|
from .os import (
|
|
43
47
|
cd,
|
|
@@ -89,20 +93,24 @@ __all__ = (
|
|
|
89
93
|
'format',
|
|
90
94
|
'save',
|
|
91
95
|
|
|
92
|
-
'
|
|
96
|
+
'compose',
|
|
97
|
+
'decompose',
|
|
98
|
+
'stack',
|
|
99
|
+
'unstack',
|
|
100
|
+
'transpose',
|
|
93
101
|
'date_delta',
|
|
94
102
|
'date_table',
|
|
95
103
|
'deduplicate',
|
|
96
|
-
'embed',
|
|
97
|
-
'flatten',
|
|
98
104
|
'get_df',
|
|
99
105
|
'get_df_patients',
|
|
100
106
|
'get_df_medications',
|
|
101
107
|
'get_df_old',
|
|
102
108
|
'get_df_new',
|
|
103
|
-
'stagger',
|
|
104
|
-
'transpose',
|
|
105
109
|
'split_col',
|
|
110
|
+
'embed',
|
|
111
|
+
'collapse',
|
|
112
|
+
'stagger',
|
|
113
|
+
'flatten',
|
|
106
114
|
|
|
107
115
|
'cd',
|
|
108
116
|
'cp',
|
|
@@ -1498,15 +1498,26 @@ def _add_retained_cols(d: Diff) -> Diff:
|
|
|
1498
1498
|
|
|
1499
1499
|
idx_shared = d._values.index.intersection(d._cols_retained.index)
|
|
1500
1500
|
df_retained = d._cols_retained.loc[idx_shared, :]
|
|
1501
|
-
d._values = pd.
|
|
1501
|
+
d._values = pd.merge(
|
|
1502
|
+
df_retained,
|
|
1503
|
+
d._values,
|
|
1504
|
+
left_index=True,
|
|
1505
|
+
right_index=True,
|
|
1506
|
+
how='right',
|
|
1507
|
+
)
|
|
1502
1508
|
|
|
1503
1509
|
df_style = pd.DataFrame(
|
|
1504
1510
|
data='',
|
|
1505
1511
|
index=df_retained.index,
|
|
1506
1512
|
columns=df_retained.columns,
|
|
1507
1513
|
)
|
|
1508
|
-
d._style = pd.
|
|
1509
|
-
|
|
1514
|
+
d._style = pd.merge(
|
|
1515
|
+
df_style,
|
|
1516
|
+
d._style,
|
|
1517
|
+
left_index=True,
|
|
1518
|
+
right_index=True,
|
|
1519
|
+
how='right',
|
|
1520
|
+
)
|
|
1510
1521
|
return d
|
|
1511
1522
|
|
|
1512
1523
|
|
|
@@ -9,13 +9,15 @@ from .qlang import (
|
|
|
9
9
|
qs,
|
|
10
10
|
)
|
|
11
11
|
from .pandas import (
|
|
12
|
+
compose,
|
|
13
|
+
decompose,
|
|
14
|
+
stack,
|
|
15
|
+
unstack,
|
|
16
|
+
transpose,
|
|
12
17
|
date_delta,
|
|
13
18
|
date_table,
|
|
14
19
|
collapse,
|
|
15
20
|
embed,
|
|
16
|
-
flatten,
|
|
17
|
-
stagger,
|
|
18
|
-
transpose,
|
|
19
21
|
split_col,
|
|
20
22
|
)
|
|
21
23
|
|
|
@@ -55,6 +57,124 @@ class DukitAccessor():
|
|
|
55
57
|
return qr(self.df, code, verbosity)
|
|
56
58
|
|
|
57
59
|
|
|
60
|
+
def compose(
|
|
61
|
+
self,
|
|
62
|
+
col: str,
|
|
63
|
+
cols: list[str],
|
|
64
|
+
template='{0}_{1}',
|
|
65
|
+
verbosity=3,
|
|
66
|
+
):
|
|
67
|
+
return compose(
|
|
68
|
+
self.df,
|
|
69
|
+
col=col,
|
|
70
|
+
cols=cols,
|
|
71
|
+
template=template,
|
|
72
|
+
verbosity=verbosity,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def stack(
|
|
77
|
+
self,
|
|
78
|
+
uid: str,
|
|
79
|
+
col_pattern: str = r'(?P<col>.+)_(?P<subid>.+)',
|
|
80
|
+
uid_template: str = '{uid}_{subid}',
|
|
81
|
+
cols_keep: list[str] | None = None,
|
|
82
|
+
dropna: bool = True,
|
|
83
|
+
sort: bool = True,
|
|
84
|
+
verbosity: int = 3,
|
|
85
|
+
) -> pd.DataFrame:
|
|
86
|
+
df_new = stack(
|
|
87
|
+
self.df,
|
|
88
|
+
uid=uid,
|
|
89
|
+
col_pattern=col_pattern,
|
|
90
|
+
uid_template=uid_template,
|
|
91
|
+
cols_keep=cols_keep,
|
|
92
|
+
dropna=dropna,
|
|
93
|
+
sort=sort,
|
|
94
|
+
verbosity=verbosity,
|
|
95
|
+
)
|
|
96
|
+
return df_new
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def unstack(
|
|
100
|
+
self,
|
|
101
|
+
uid: str,
|
|
102
|
+
uid_pattern: str = r'(?P<uid>.+)_(?P<subid>.+)',
|
|
103
|
+
col_template: str = '{col}_{subid}',
|
|
104
|
+
cols_keep: list[str] | None = None,
|
|
105
|
+
stagger: bool = True,
|
|
106
|
+
verbosity: int = 3,
|
|
107
|
+
) -> pd.DataFrame:
|
|
108
|
+
df_new = unstack(
|
|
109
|
+
self.df,
|
|
110
|
+
uid=uid,
|
|
111
|
+
uid_pattern=uid_pattern,
|
|
112
|
+
col_template=col_template,
|
|
113
|
+
cols_keep=cols_keep,
|
|
114
|
+
stagger=stagger,
|
|
115
|
+
verbosity=verbosity
|
|
116
|
+
)
|
|
117
|
+
return df_new
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def transpose(
|
|
121
|
+
self,
|
|
122
|
+
header='uid',
|
|
123
|
+
) -> pd.DataFrame:
|
|
124
|
+
df_new = transpose(
|
|
125
|
+
self.df,
|
|
126
|
+
header=header,
|
|
127
|
+
)
|
|
128
|
+
return df_new
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def save(
|
|
132
|
+
self,
|
|
133
|
+
path: str,
|
|
134
|
+
sheet_name='df',
|
|
135
|
+
index=False,
|
|
136
|
+
format_excel=True,
|
|
137
|
+
freeze_panes='B2',
|
|
138
|
+
hide_sheets=None,
|
|
139
|
+
hide_cols=None,
|
|
140
|
+
col_width_max=70,
|
|
141
|
+
col_width_padding=2,
|
|
142
|
+
align_vertical='top',
|
|
143
|
+
align_horizontal='left',
|
|
144
|
+
wrap_text=True,
|
|
145
|
+
**kwargs,
|
|
146
|
+
):
|
|
147
|
+
save(
|
|
148
|
+
df=self.df,
|
|
149
|
+
path=path,
|
|
150
|
+
sheet_name=sheet_name,
|
|
151
|
+
index=index,
|
|
152
|
+
format_excel=format_excel,
|
|
153
|
+
freeze_panes=freeze_panes,
|
|
154
|
+
hide_sheets=hide_sheets,
|
|
155
|
+
hide_cols=hide_cols,
|
|
156
|
+
col_width_max=col_width_max,
|
|
157
|
+
col_width_padding=col_width_padding,
|
|
158
|
+
align_vertical=align_vertical,
|
|
159
|
+
align_horizontal=align_horizontal,
|
|
160
|
+
wrap_text=wrap_text,
|
|
161
|
+
**kwargs,
|
|
162
|
+
)
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def style(self):
|
|
167
|
+
code = r"""
|
|
168
|
+
.replace("\n", "<br>")
|
|
169
|
+
.wrap(normal)
|
|
170
|
+
.mono()
|
|
171
|
+
.align(left)
|
|
172
|
+
%.align(center)
|
|
173
|
+
"""
|
|
174
|
+
df_styled = qs(self.df, code)
|
|
175
|
+
return df_styled
|
|
176
|
+
|
|
177
|
+
|
|
58
178
|
def date_delta(
|
|
59
179
|
self,
|
|
60
180
|
reference_date: str | datetime.date | pd.Timestamp = None,
|
|
@@ -135,45 +255,6 @@ class DukitAccessor():
|
|
|
135
255
|
return df_new
|
|
136
256
|
|
|
137
257
|
|
|
138
|
-
def flatten(
|
|
139
|
-
self,
|
|
140
|
-
on: str,
|
|
141
|
-
template='{colname}_#{counter}',
|
|
142
|
-
):
|
|
143
|
-
df_new = flatten(
|
|
144
|
-
self.df,
|
|
145
|
-
on=on,
|
|
146
|
-
template=template,
|
|
147
|
-
)
|
|
148
|
-
return df_new
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
def stagger(
|
|
152
|
-
self,
|
|
153
|
-
on: str,
|
|
154
|
-
template='#{counter}_{colname}',
|
|
155
|
-
separator_col='#{counter}',
|
|
156
|
-
):
|
|
157
|
-
df_new = stagger(
|
|
158
|
-
self.df,
|
|
159
|
-
on=on,
|
|
160
|
-
template=template,
|
|
161
|
-
separator_col=separator_col,
|
|
162
|
-
)
|
|
163
|
-
return df_new
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def transpose(
|
|
167
|
-
self,
|
|
168
|
-
header='uid',
|
|
169
|
-
) -> pd.DataFrame:
|
|
170
|
-
df_new = transpose(
|
|
171
|
-
self.df,
|
|
172
|
-
header=header,
|
|
173
|
-
)
|
|
174
|
-
return df_new
|
|
175
|
-
|
|
176
|
-
|
|
177
258
|
def split_col(
|
|
178
259
|
self,
|
|
179
260
|
col: str = 'uid',
|
|
@@ -189,50 +270,3 @@ class DukitAccessor():
|
|
|
189
270
|
new2=new2,
|
|
190
271
|
)
|
|
191
272
|
return df_new
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def save(
|
|
195
|
-
self,
|
|
196
|
-
path: str,
|
|
197
|
-
sheet_name='df',
|
|
198
|
-
index=False,
|
|
199
|
-
format_excel=True,
|
|
200
|
-
freeze_panes='B2',
|
|
201
|
-
hide_sheets=None,
|
|
202
|
-
hide_cols=None,
|
|
203
|
-
col_width_max=70,
|
|
204
|
-
col_width_padding=2,
|
|
205
|
-
align_vertical='top',
|
|
206
|
-
align_horizontal='left',
|
|
207
|
-
wrap_text=True,
|
|
208
|
-
**kwargs,
|
|
209
|
-
):
|
|
210
|
-
save(
|
|
211
|
-
df=self.df,
|
|
212
|
-
path=path,
|
|
213
|
-
sheet_name=sheet_name,
|
|
214
|
-
index=index,
|
|
215
|
-
format_excel=format_excel,
|
|
216
|
-
freeze_panes=freeze_panes,
|
|
217
|
-
hide_sheets=hide_sheets,
|
|
218
|
-
hide_cols=hide_cols,
|
|
219
|
-
col_width_max=col_width_max,
|
|
220
|
-
col_width_padding=col_width_padding,
|
|
221
|
-
align_vertical=align_vertical,
|
|
222
|
-
align_horizontal=align_horizontal,
|
|
223
|
-
wrap_text=wrap_text,
|
|
224
|
-
**kwargs,
|
|
225
|
-
)
|
|
226
|
-
return None
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
def style(self):
|
|
230
|
-
code = r"""
|
|
231
|
-
.replace("\n", "<br>")
|
|
232
|
-
.wrap(normal)
|
|
233
|
-
.mono()
|
|
234
|
-
.align(left)
|
|
235
|
-
%.align(center)
|
|
236
|
-
"""
|
|
237
|
-
df_styled = qs(self.df, code)
|
|
238
|
-
return df_styled
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
|
|
2
|
+
import re
|
|
2
3
|
import copy
|
|
3
4
|
import datetime
|
|
4
5
|
import numpy as np
|
|
5
6
|
import pandas as pd
|
|
6
7
|
|
|
7
|
-
from .utils import
|
|
8
|
-
|
|
8
|
+
from .utils import (
|
|
9
|
+
log,
|
|
10
|
+
ensure_unique_string,
|
|
11
|
+
GREEN_LIGHT,
|
|
12
|
+
)
|
|
13
|
+
from .typing import (
|
|
14
|
+
date_,
|
|
15
|
+
list_,
|
|
16
|
+
)
|
|
9
17
|
|
|
10
18
|
|
|
11
19
|
def get_df() -> pd.DataFrame:
|
|
@@ -295,6 +303,519 @@ def get_df_new() -> pd.DataFrame:
|
|
|
295
303
|
|
|
296
304
|
|
|
297
305
|
|
|
306
|
+
def deduplicate(
|
|
307
|
+
obj,
|
|
308
|
+
name='object',
|
|
309
|
+
rename_all=True,
|
|
310
|
+
start_at_1=True,
|
|
311
|
+
verbosity=3,
|
|
312
|
+
):
|
|
313
|
+
"""
|
|
314
|
+
make values unique by appending consecutive numeric suffixes.
|
|
315
|
+
|
|
316
|
+
Parameters
|
|
317
|
+
----------
|
|
318
|
+
obj : object
|
|
319
|
+
object convertible to a pandas Series.
|
|
320
|
+
name : str, default 'object'
|
|
321
|
+
name used in diagnostic messages.
|
|
322
|
+
rename_all : bool, default True
|
|
323
|
+
whether to rename all values, not just duplicates.
|
|
324
|
+
start_at_1 : bool, default True
|
|
325
|
+
whether to start numbering at 0 or 1.
|
|
326
|
+
verbosity : int, default 3
|
|
327
|
+
logging verbosity level.
|
|
328
|
+
|
|
329
|
+
Returns
|
|
330
|
+
-------
|
|
331
|
+
object
|
|
332
|
+
deduplicated object, converted back to its original type when
|
|
333
|
+
possible. Values are converted to strings during processing.
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
obj = copy.deepcopy(obj)
|
|
337
|
+
class_orig = obj.__class__
|
|
338
|
+
obj = _to_series(obj)
|
|
339
|
+
|
|
340
|
+
#Values can only be deduplicated if index is unique
|
|
341
|
+
if not obj.index.is_unique:
|
|
342
|
+
msg = (
|
|
343
|
+
f'info: duplicates found in index of {name}.'
|
|
344
|
+
' deduplicating by appending consecutive numbers.'
|
|
345
|
+
)
|
|
346
|
+
log(msg, 'dk.pandas.deduplicate', verbosity)
|
|
347
|
+
obj.index = _deduplicate(
|
|
348
|
+
_to_series(obj.index),
|
|
349
|
+
rename_all,
|
|
350
|
+
start_at_1,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
if rename_all:
|
|
354
|
+
obj = _deduplicate(
|
|
355
|
+
obj,
|
|
356
|
+
rename_all,
|
|
357
|
+
start_at_1,
|
|
358
|
+
)
|
|
359
|
+
rounds = 1
|
|
360
|
+
else:
|
|
361
|
+
rounds = 0
|
|
362
|
+
|
|
363
|
+
while not obj.is_unique:
|
|
364
|
+
if rounds == 0:
|
|
365
|
+
msg = (
|
|
366
|
+
f'debug: duplicates found in {name}.'
|
|
367
|
+
' deduplicating by appending consecutive numbers.'
|
|
368
|
+
)
|
|
369
|
+
else:
|
|
370
|
+
msg = (
|
|
371
|
+
f'debug: duplicates still found in {name} after'
|
|
372
|
+
f' {rounds} deduplication rounds.'
|
|
373
|
+
' deduplicating again by appending consecutive numbers.'
|
|
374
|
+
)
|
|
375
|
+
log(msg, 'dk.pandas.deduplicate', verbosity)
|
|
376
|
+
obj = _deduplicate(
|
|
377
|
+
obj,
|
|
378
|
+
rename_all,
|
|
379
|
+
start_at_1,
|
|
380
|
+
)
|
|
381
|
+
rounds += 1
|
|
382
|
+
|
|
383
|
+
if not isinstance(obj, class_orig):
|
|
384
|
+
try:
|
|
385
|
+
obj = class_orig(obj)
|
|
386
|
+
except Exception as e:
|
|
387
|
+
msg = (
|
|
388
|
+
'Error: could not convert deduplicated object'
|
|
389
|
+
f' back to original type {class_orig}: {e}'
|
|
390
|
+
)
|
|
391
|
+
log(msg, 'dk.pandas.deduplicate', verbosity)
|
|
392
|
+
|
|
393
|
+
return obj
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def _to_series(obj, verbosity=3):
|
|
397
|
+
try:
|
|
398
|
+
obj = pd.Series(obj, dtype=str)
|
|
399
|
+
except Exception as e:
|
|
400
|
+
msg = (
|
|
401
|
+
'Error: could not convert input of type'
|
|
402
|
+
f' {type(obj)} to Series: {e}'
|
|
403
|
+
)
|
|
404
|
+
log(msg, 'dk.pandas._to_series', verbosity)
|
|
405
|
+
return obj
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _deduplicate(series, rename_all=True, start_at_1=True):
|
|
409
|
+
cumulative_count = series.groupby(series).cumcount()
|
|
410
|
+
if rename_all:
|
|
411
|
+
if start_at_1:
|
|
412
|
+
cumulative_count += 1
|
|
413
|
+
series = (
|
|
414
|
+
series.astype(str)
|
|
415
|
+
+ '_'
|
|
416
|
+
+ cumulative_count.astype(str)
|
|
417
|
+
)
|
|
418
|
+
else:
|
|
419
|
+
duplicates_mask = series.index[cumulative_count > 0]
|
|
420
|
+
duplicates = series[duplicates_mask]
|
|
421
|
+
duplicates_new = (
|
|
422
|
+
duplicates.astype(str)
|
|
423
|
+
+ '_'
|
|
424
|
+
+ cumulative_count[duplicates_mask].astype(str)
|
|
425
|
+
)
|
|
426
|
+
series[duplicates_mask] = duplicates_new
|
|
427
|
+
return series
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def compose(
|
|
432
|
+
df: pd.DataFrame,
|
|
433
|
+
col: str,
|
|
434
|
+
cols: list[str],
|
|
435
|
+
template='{0}_{1}',
|
|
436
|
+
verbosity=3,
|
|
437
|
+
):
|
|
438
|
+
|
|
439
|
+
_compose_check_if_col_alread_exists(df, col, verbosity)
|
|
440
|
+
_compose_check_cols_matches_template(cols, template, verbosity)
|
|
441
|
+
pos = _compose_get_first_col_pos(df, cols, verbosity)
|
|
442
|
+
|
|
443
|
+
col_new = df[cols].apply(lambda row: template.format(*row), axis='columns')
|
|
444
|
+
df.insert(pos, col, col_new)
|
|
445
|
+
df.drop(columns=cols, inplace=True)
|
|
446
|
+
|
|
447
|
+
return df
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def _compose_check_cols_matches_template(cols, template, verbosity):
|
|
451
|
+
num_placeholders = template.count('{')
|
|
452
|
+
if num_placeholders != len(cols):
|
|
453
|
+
msg = (
|
|
454
|
+
'WARNING: Number of placeholders in template'
|
|
455
|
+
f' ({num_placeholders}) does not match number'
|
|
456
|
+
f' of cols ({len(cols)}) provided.'
|
|
457
|
+
)
|
|
458
|
+
log(msg, 'dk.pandas.compose', verbosity)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def _compose_check_if_col_alread_exists(df, col, verbosity):
|
|
462
|
+
if col in df.columns:
|
|
463
|
+
msg = f'WARNING: col "{col}" already exists and will be overwritten.'
|
|
464
|
+
log(msg, 'dk.pandas.compose', verbosity)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _compose_get_first_col_pos(df, cols, verbosity=3):
|
|
468
|
+
pos = len(df.columns)
|
|
469
|
+
for col_temp in cols:
|
|
470
|
+
if col_temp not in df.columns:
|
|
471
|
+
msg = f'ERROR: col "{col_temp}" does not exist.'
|
|
472
|
+
log(msg, 'dk.pandas.compose', verbosity)
|
|
473
|
+
raise ValueError(f'Col "{col_temp}" does not exist.')
|
|
474
|
+
pos_temp = df.columns.get_loc(col_temp)
|
|
475
|
+
if pos_temp < pos:
|
|
476
|
+
pos = pos_temp
|
|
477
|
+
return pos
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def decompose(
|
|
482
|
+
df: pd.DataFrame,
|
|
483
|
+
col: str,
|
|
484
|
+
cols: list[str] | None = None,
|
|
485
|
+
pattern='(.+)_(.+)',
|
|
486
|
+
verbosity=3,
|
|
487
|
+
):
|
|
488
|
+
|
|
489
|
+
_decomposes_check_if_col_exists(df, col, verbosity)
|
|
490
|
+
|
|
491
|
+
pos = df.columns.get_loc(col)
|
|
492
|
+
df_unmerged = df[col].str.extract(pattern)
|
|
493
|
+
|
|
494
|
+
if cols is None:
|
|
495
|
+
cols = [
|
|
496
|
+
f'{col}_{group}'for group
|
|
497
|
+
in range(1, len(df_unmerged.columns) + 1)
|
|
498
|
+
]
|
|
499
|
+
else:
|
|
500
|
+
_decomposes_check_cols_matches_extraction(cols, df_unmerged, verbosity)
|
|
501
|
+
_decomposes_check_cols_already_exists(df, cols, verbosity)
|
|
502
|
+
df_unmerged.columns = cols
|
|
503
|
+
|
|
504
|
+
df1 = df.iloc[:, :pos]
|
|
505
|
+
df2 = df.iloc[:, pos+1:]
|
|
506
|
+
df_new = pd.concat([df1, df_unmerged, df2], axis='columns')
|
|
507
|
+
|
|
508
|
+
return df_new
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def _decomposes_check_if_col_exists(df, col, verbosity):
|
|
512
|
+
if col not in df.columns:
|
|
513
|
+
msg = f'ERROR: col "{col}" does not exist.'
|
|
514
|
+
log(msg, 'dk.pandas.decompose', verbosity)
|
|
515
|
+
raise ValueError(msg)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def _decomposes_check_cols_matches_extraction(cols, df_unmerged, verbosity):
|
|
519
|
+
if len(cols) != len(df_unmerged.columns):
|
|
520
|
+
msg = (
|
|
521
|
+
f'ERROR: Number of columns provided ({len(cols)}) '
|
|
522
|
+
'does not match number of groups extracted '
|
|
523
|
+
f'({len(df_unmerged.columns)}).'
|
|
524
|
+
)
|
|
525
|
+
log(msg, 'dk.pandas.decompose', verbosity)
|
|
526
|
+
raise ValueError(msg)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def _decomposes_check_cols_already_exists(df, cols, verbosity):
|
|
530
|
+
for col_temp in cols:
|
|
531
|
+
if col_temp in df.columns:
|
|
532
|
+
msg = f'WARNING: col "{col_temp}" already exists and will be overwritten.'
|
|
533
|
+
log(msg, 'dk.pandas.decompose', verbosity)
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def stack(
|
|
538
|
+
df: pd.DataFrame,
|
|
539
|
+
uid: str,
|
|
540
|
+
col_pattern: str = r'(?P<col>.+)_(?P<subid>.+)',
|
|
541
|
+
uid_template: str = '{uid}_{subid}',
|
|
542
|
+
cols_keep: list[str] | None = None,
|
|
543
|
+
dropna: bool = True,
|
|
544
|
+
sort: bool = True,
|
|
545
|
+
verbosity: int = 3,
|
|
546
|
+
) -> pd.DataFrame:
|
|
547
|
+
|
|
548
|
+
df = df.copy()
|
|
549
|
+
df[uid] = df[uid].astype('string')
|
|
550
|
+
_stack_check_uid_unique1(df, uid, verbosity)
|
|
551
|
+
|
|
552
|
+
uid_old = ensure_unique_string(uid, df.columns)
|
|
553
|
+
df.set_index(uid, inplace=True)
|
|
554
|
+
df.index.name = uid_old
|
|
555
|
+
cols_keep = list_(cols_keep)
|
|
556
|
+
cols_stack = [col for col in df.columns if col not in cols_keep]
|
|
557
|
+
|
|
558
|
+
non_na_vals = int(
|
|
559
|
+
df[cols_stack]
|
|
560
|
+
.notna()
|
|
561
|
+
.sum()
|
|
562
|
+
.sum()
|
|
563
|
+
)
|
|
564
|
+
|
|
565
|
+
subdfs = {}
|
|
566
|
+
not_matched = []
|
|
567
|
+
for col_old in cols_stack:
|
|
568
|
+
match = re.match(col_pattern, col_old)
|
|
569
|
+
if match:
|
|
570
|
+
subid_val = match.group('subid')
|
|
571
|
+
col_new = match.group('col')
|
|
572
|
+
if subid_val not in subdfs:
|
|
573
|
+
subdfs[subid_val] = pd.DataFrame()
|
|
574
|
+
subdfs[subid_val][col_new] = df[col_old]
|
|
575
|
+
else:
|
|
576
|
+
not_matched.append(col_old)
|
|
577
|
+
_stack_check_successfull_match(not_matched, verbosity)
|
|
578
|
+
|
|
579
|
+
for subid_val in subdfs.keys():
|
|
580
|
+
subdf = subdfs[subid_val]
|
|
581
|
+
if dropna:
|
|
582
|
+
subdf.dropna(
|
|
583
|
+
inplace=True,
|
|
584
|
+
how='all',
|
|
585
|
+
)
|
|
586
|
+
uid_vals = [
|
|
587
|
+
uid_template.format(uid=uid_old, subid=subid_val)
|
|
588
|
+
for uid_old in subdf.index
|
|
589
|
+
]
|
|
590
|
+
subdf.insert(0, uid, uid_vals)
|
|
591
|
+
|
|
592
|
+
df_new = pd.concat(list(subdfs.values()), axis='index')
|
|
593
|
+
df_new = pd.merge(
|
|
594
|
+
df[cols_keep].reset_index(),
|
|
595
|
+
df_new.reset_index(),
|
|
596
|
+
on=uid_old,
|
|
597
|
+
how='right',
|
|
598
|
+
)
|
|
599
|
+
df_new.drop(columns=[uid_old], inplace=True)
|
|
600
|
+
|
|
601
|
+
_stack_check_missed_values(df_new, uid, cols_keep, non_na_vals, verbosity)
|
|
602
|
+
|
|
603
|
+
if sort:
|
|
604
|
+
df_new.sort_values(by=uid, inplace=True)
|
|
605
|
+
df_new.reset_index(drop=True, inplace=True)
|
|
606
|
+
|
|
607
|
+
_stack_check_uid_unique2(uid, verbosity, df_new)
|
|
608
|
+
|
|
609
|
+
return df_new
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def _stack_check_uid_unique1(df, uid, verbosity):
|
|
613
|
+
if not df[uid].is_unique:
|
|
614
|
+
non_unique = df[uid][df[uid].duplicated()].unique()
|
|
615
|
+
msg = (
|
|
616
|
+
f'WARNING: Source uid col "{uid}" '
|
|
617
|
+
f'contains non-unique values: {non_unique}'
|
|
618
|
+
)
|
|
619
|
+
log(msg, 'dk.pandas.stack', verbosity)
|
|
620
|
+
|
|
621
|
+
|
|
622
|
+
def _stack_check_successfull_match(not_matched, verbosity):
|
|
623
|
+
if len(not_matched) > 0:
|
|
624
|
+
msg = (
|
|
625
|
+
'WARNING: Some cols did not '
|
|
626
|
+
f'match col_pattern: {not_matched}'
|
|
627
|
+
)
|
|
628
|
+
log(msg, 'dk.pandas.stack', verbosity)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def _stack_check_missed_values(df_new, uid, cols_keep, non_na_vals1, verbosity):
|
|
632
|
+
cols_stacked = [
|
|
633
|
+
col for col
|
|
634
|
+
in df_new.columns
|
|
635
|
+
if col not in cols_keep + [uid]
|
|
636
|
+
]
|
|
637
|
+
non_na_vals2 = int(
|
|
638
|
+
df_new[cols_stacked]
|
|
639
|
+
.notna()
|
|
640
|
+
.sum()
|
|
641
|
+
.sum()
|
|
642
|
+
)
|
|
643
|
+
if non_na_vals1 != non_na_vals2:
|
|
644
|
+
msg = (
|
|
645
|
+
f'WARNING: Non-NA value mismatch: '
|
|
646
|
+
f'before={non_na_vals1}, after={non_na_vals2}'
|
|
647
|
+
)
|
|
648
|
+
log(msg, 'dk.pandas.stack', verbosity)
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def _stack_check_uid_unique2(uid, verbosity, df_new):
|
|
652
|
+
if not df_new[uid].is_unique:
|
|
653
|
+
non_unique = df_new[uid][df_new[uid].duplicated()].unique()
|
|
654
|
+
msg = (
|
|
655
|
+
f'WARNING: Result uid col "{uid}" '
|
|
656
|
+
f'contains non-unique values: {non_unique}'
|
|
657
|
+
)
|
|
658
|
+
log(msg, 'dk.pandas.stack', verbosity)
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
def unstack(
|
|
663
|
+
df: pd.DataFrame,
|
|
664
|
+
uid: str,
|
|
665
|
+
uid_pattern: str = r'(?P<uid>.+)_(?P<subid>.+)',
|
|
666
|
+
col_template: str = '{col}_{subid}',
|
|
667
|
+
cols_keep: list[str] | None = None,
|
|
668
|
+
stagger: bool = True,
|
|
669
|
+
verbosity: int = 3,
|
|
670
|
+
) -> pd.DataFrame:
|
|
671
|
+
|
|
672
|
+
df = df.copy()
|
|
673
|
+
df[uid] = df[uid].astype('string')
|
|
674
|
+
_unstack_check_uid_unique(df, uid, verbosity)
|
|
675
|
+
|
|
676
|
+
ids = df[uid].str.extract(uid_pattern)
|
|
677
|
+
expected_cols = ['uid', 'subid']
|
|
678
|
+
_unstack_check_successfull_extraction(df, uid, ids, expected_cols, verbosity)
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
ids.set_index('uid', inplace=True)
|
|
682
|
+
df[uid] = ids.index
|
|
683
|
+
df.set_index(uid, inplace=True)
|
|
684
|
+
subid_col = ids['subid']
|
|
685
|
+
subid_vals = subid_col.unique()
|
|
686
|
+
cols_keep = list_(cols_keep)
|
|
687
|
+
cols_unstack = [col for col in df.columns if col not in cols_keep]
|
|
688
|
+
|
|
689
|
+
non_na_vals = int(
|
|
690
|
+
df[cols_unstack]
|
|
691
|
+
.notna()
|
|
692
|
+
.sum()
|
|
693
|
+
.sum()
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
cols_unstacked = []
|
|
697
|
+
if stagger:
|
|
698
|
+
for subid_val in subid_vals:
|
|
699
|
+
mask = subid_col == subid_val
|
|
700
|
+
for col_old in cols_unstack:
|
|
701
|
+
col_unstacked = df.loc[mask, col_old]
|
|
702
|
+
col_unstacked.name = col_template.format(col=col_old, subid=subid_val)
|
|
703
|
+
cols_unstacked.append(col_unstacked)
|
|
704
|
+
else:
|
|
705
|
+
for col_old in cols_unstack:
|
|
706
|
+
for subid_val in subid_vals:
|
|
707
|
+
mask = subid_col == subid_val
|
|
708
|
+
col_unstacked = df.loc[mask, col_old]
|
|
709
|
+
col_unstacked.name = col_template.format(col=col_old, subid=subid_val)
|
|
710
|
+
cols_unstacked.append(col_unstacked)
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
df_new = df.loc[~df.index.duplicated(keep='first'), cols_keep]
|
|
714
|
+
for col_unstacked in cols_unstacked:
|
|
715
|
+
df_new = pd.merge(
|
|
716
|
+
df_new,
|
|
717
|
+
col_unstacked,
|
|
718
|
+
left_index=True,
|
|
719
|
+
right_index=True,
|
|
720
|
+
how='left'
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
_unstack_check_missed_values(uid, cols_keep, verbosity, non_na_vals, df_new)
|
|
724
|
+
|
|
725
|
+
df_new.reset_index(inplace=True)
|
|
726
|
+
|
|
727
|
+
return df_new
|
|
728
|
+
|
|
729
|
+
|
|
730
|
+
def _unstack_check_uid_unique(df, uid, verbosity):
|
|
731
|
+
if not df[uid].is_unique:
|
|
732
|
+
non_unique = (
|
|
733
|
+
df[uid]
|
|
734
|
+
[df[uid].duplicated()]
|
|
735
|
+
.unique()
|
|
736
|
+
.tolist()
|
|
737
|
+
)
|
|
738
|
+
msg = (
|
|
739
|
+
f'INFO: deduplicating non-unique values '
|
|
740
|
+
f'in source uid col "{uid}": {non_unique}'
|
|
741
|
+
)
|
|
742
|
+
log(msg, 'dk.pandas.unstack', verbosity)
|
|
743
|
+
df[uid] = deduplicate(df[uid])
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def _unstack_check_successfull_extraction(df, uid, ids, expected_cols, verbosity):
|
|
747
|
+
if list(ids.columns) != expected_cols:
|
|
748
|
+
msg = (
|
|
749
|
+
'WARNING: uid pattern must contain exactly 2 named '
|
|
750
|
+
'match groups, one called uid and one called subid. '
|
|
751
|
+
'use the subid group in col_template.'
|
|
752
|
+
)
|
|
753
|
+
log(msg, 'dk.pandas.unstack', verbosity)
|
|
754
|
+
else:
|
|
755
|
+
uid_isna = ids.iloc[:, 0].isna()
|
|
756
|
+
uids_unmatched = df.loc[uid_isna, uid].tolist()
|
|
757
|
+
if uid_isna.any():
|
|
758
|
+
msg = (
|
|
759
|
+
'WARNING: Some uids did not match '
|
|
760
|
+
f'uid_pattern: {uids_unmatched}'
|
|
761
|
+
)
|
|
762
|
+
log(msg, 'dk.pandas.unstack', verbosity)
|
|
763
|
+
|
|
764
|
+
|
|
765
|
+
def _unstack_check_missed_values(uid, cols_keep, verbosity, non_na_vals1, df_new):
|
|
766
|
+
cols_stacked = [
|
|
767
|
+
col for col
|
|
768
|
+
in df_new.columns
|
|
769
|
+
if col not in cols_keep + [uid]
|
|
770
|
+
]
|
|
771
|
+
non_na_vals2 = int(
|
|
772
|
+
df_new[cols_stacked]
|
|
773
|
+
.notna()
|
|
774
|
+
.sum()
|
|
775
|
+
.sum()
|
|
776
|
+
)
|
|
777
|
+
if non_na_vals1 != non_na_vals2:
|
|
778
|
+
msg = (
|
|
779
|
+
'WARNING: Non-NA value mismatch: '
|
|
780
|
+
f'before={non_na_vals1}, after={non_na_vals2}'
|
|
781
|
+
)
|
|
782
|
+
log(msg, 'dk.pandas.unstack', verbosity)
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
def transpose(
|
|
787
|
+
df: pd.DataFrame,
|
|
788
|
+
header='uid',
|
|
789
|
+
) -> pd.DataFrame:
|
|
790
|
+
"""
|
|
791
|
+
transpose a df while preserving a designated header col.
|
|
792
|
+
|
|
793
|
+
Parameters
|
|
794
|
+
----------
|
|
795
|
+
df : pandas.DataFrame
|
|
796
|
+
df to transpose.
|
|
797
|
+
header : str, default 'uid'
|
|
798
|
+
col used as the new header identifier.
|
|
799
|
+
|
|
800
|
+
Returns
|
|
801
|
+
-------
|
|
802
|
+
pandas.DataFrame
|
|
803
|
+
transposed df.
|
|
804
|
+
"""
|
|
805
|
+
|
|
806
|
+
cols_old = df.columns
|
|
807
|
+
cols_new = df[header].values
|
|
808
|
+
|
|
809
|
+
df = df.T
|
|
810
|
+
df.columns = cols_new
|
|
811
|
+
df.insert(0, header, cols_old)
|
|
812
|
+
df.drop(header, axis=0, inplace=True)
|
|
813
|
+
df.reset_index(drop=True, inplace=True)
|
|
814
|
+
|
|
815
|
+
return df
|
|
816
|
+
|
|
817
|
+
|
|
818
|
+
|
|
298
819
|
def date_delta(
|
|
299
820
|
df: pd.DataFrame,
|
|
300
821
|
reference_date: str | datetime.date | pd.Timestamp = None,
|
|
@@ -493,19 +1014,19 @@ def date_table(
|
|
|
493
1014
|
cols_records = [
|
|
494
1015
|
col for col
|
|
495
1016
|
in df_timeline.columns
|
|
496
|
-
if col not in ('
|
|
1017
|
+
if col not in ('meta', 'days', 'planned')
|
|
497
1018
|
]
|
|
498
1019
|
df_timeline.insert(
|
|
499
1020
|
loc=0,
|
|
500
|
-
column='
|
|
1021
|
+
column='meta',
|
|
501
1022
|
value=''
|
|
502
1023
|
)
|
|
503
1024
|
has_event = df_timeline[cols_records] != filler
|
|
504
1025
|
records_per_row = has_event.sum(axis='columns')
|
|
505
1026
|
rows_with_records = records_per_row > 0
|
|
506
1027
|
summary = 'records: ' + records_per_row.astype(str)
|
|
507
|
-
df_timeline.loc[rows_with_records, '
|
|
508
|
-
df_timeline['
|
|
1028
|
+
df_timeline.loc[rows_with_records, 'meta'] = summary
|
|
1029
|
+
df_timeline['meta'] = df_timeline['meta'].fillna('')
|
|
509
1030
|
|
|
510
1031
|
if schedule is not None:
|
|
511
1032
|
df_timeline = _highlight_schedule(df_timeline, schedule)
|
|
@@ -548,93 +1069,39 @@ def _highlight_schedule(
|
|
|
548
1069
|
|
|
549
1070
|
|
|
550
1071
|
|
|
1072
|
+
def split_col(
|
|
1073
|
+
df: pd.DataFrame,
|
|
1074
|
+
col: str = 'uid',
|
|
1075
|
+
sep: str = '::\n',
|
|
1076
|
+
new1: str = None,
|
|
1077
|
+
new2: str = 'label',
|
|
1078
|
+
):
|
|
551
1079
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
obj : object
|
|
559
|
-
object convertible to a pandas Series.
|
|
560
|
-
name : str, default 'object'
|
|
561
|
-
name used in diagnostic messages.
|
|
562
|
-
verbosity : int, default 3
|
|
563
|
-
logging verbosity level.
|
|
564
|
-
|
|
565
|
-
Returns
|
|
566
|
-
-------
|
|
567
|
-
object
|
|
568
|
-
deduplicated object, converted back to its original type when
|
|
569
|
-
possible. Values are converted to strings during processing.
|
|
570
|
-
"""
|
|
571
|
-
|
|
572
|
-
obj = copy.deepcopy(obj)
|
|
573
|
-
class_orig = obj.__class__
|
|
574
|
-
obj = _to_series(obj)
|
|
575
|
-
|
|
576
|
-
#Values can only be deduplicated if index is unique
|
|
577
|
-
if not obj.index.is_unique:
|
|
578
|
-
msg = (
|
|
579
|
-
f'info: duplicates found in index of {name}.'
|
|
580
|
-
' deduplicating by appending consecutive numbers.'
|
|
581
|
-
)
|
|
582
|
-
log(msg, 'dk.pandas.deduplicate', verbosity)
|
|
583
|
-
obj.index = _deduplicate(_to_series(obj.index))
|
|
584
|
-
|
|
585
|
-
rounds = 0
|
|
586
|
-
while not obj.is_unique:
|
|
587
|
-
if rounds == 0:
|
|
588
|
-
msg = (
|
|
589
|
-
f'debug: duplicates found in {name}.'
|
|
590
|
-
' deduplicating by appending consecutive numbers.'
|
|
591
|
-
)
|
|
592
|
-
else:
|
|
593
|
-
msg = (
|
|
594
|
-
f'debug: duplicates still found in {name} after'
|
|
595
|
-
f' {rounds} deduplication rounds.'
|
|
596
|
-
' deduplicating again by appending consecutive numbers.'
|
|
597
|
-
)
|
|
598
|
-
log(msg, 'dk.pandas.deduplicate', verbosity)
|
|
599
|
-
obj = _deduplicate(obj)
|
|
600
|
-
rounds += 1
|
|
601
|
-
|
|
602
|
-
if not isinstance(obj, class_orig):
|
|
603
|
-
try:
|
|
604
|
-
obj = class_orig(obj)
|
|
605
|
-
except Exception as e:
|
|
606
|
-
msg = (
|
|
607
|
-
'Error: could not convert deduplicated object'
|
|
608
|
-
f' back to original type {class_orig}: {e}'
|
|
609
|
-
)
|
|
610
|
-
log(msg, 'dk.pandas.deduplicate', verbosity)
|
|
611
|
-
|
|
612
|
-
return obj
|
|
1080
|
+
if col not in df.columns:
|
|
1081
|
+
log(f'ERROR: no col named "{col}" found')
|
|
1082
|
+
return df
|
|
1083
|
+
elif df.columns.tolist().count(col) > 1:
|
|
1084
|
+
log(f'ERROR: multiple cols named "{col}" found')
|
|
1085
|
+
return df
|
|
613
1086
|
|
|
1087
|
+
df = df.copy()
|
|
1088
|
+
pos = df.columns.get_loc(col)
|
|
1089
|
+
splits = df[col].str.partition(sep)
|
|
614
1090
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
return obj
|
|
1091
|
+
df.drop(columns=[col], inplace=True)
|
|
1092
|
+
if new1 is None:
|
|
1093
|
+
new1 = col
|
|
1094
|
+
elif new1 in df.columns:
|
|
1095
|
+
log(f'WARNING: Col {new1} already exists, choosing new name.')
|
|
1096
|
+
new1 = ensure_unique_string(new1, df.columns.tolist())
|
|
1097
|
+
if new2 in df.columns:
|
|
1098
|
+
log(f'WARNING: Col {new2} already exists, choosing new name.')
|
|
1099
|
+
new2 = ensure_unique_string(new2, df.columns.tolist() + [new1])
|
|
625
1100
|
|
|
1101
|
+
df.insert(pos, new1, splits[0]) #type: ignore
|
|
1102
|
+
df.insert(pos + 1, new2, splits[2]) #type: ignore
|
|
626
1103
|
|
|
627
|
-
|
|
628
|
-
cumulative_count = series.groupby(series).cumcount()
|
|
629
|
-
duplicates_mask = series.index[cumulative_count > 0]
|
|
630
|
-
duplicates = series[duplicates_mask]
|
|
631
|
-
duplicates_new = (
|
|
632
|
-
duplicates.astype(str)
|
|
633
|
-
+ '_'
|
|
634
|
-
+ cumulative_count[duplicates_mask].astype(str)
|
|
635
|
-
)
|
|
636
|
-
series[duplicates_mask] = duplicates_new
|
|
637
|
-
return series
|
|
1104
|
+
return df
|
|
638
1105
|
|
|
639
1106
|
|
|
640
1107
|
|
|
@@ -854,72 +1321,3 @@ def _get_from_list(x, i):
|
|
|
854
1321
|
return x[i]
|
|
855
1322
|
else:
|
|
856
1323
|
return None
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
def transpose(
|
|
861
|
-
df: pd.DataFrame,
|
|
862
|
-
header='uid',
|
|
863
|
-
) -> pd.DataFrame:
|
|
864
|
-
"""
|
|
865
|
-
transpose a df while preserving a designated header col.
|
|
866
|
-
|
|
867
|
-
Parameters
|
|
868
|
-
----------
|
|
869
|
-
df : pandas.DataFrame
|
|
870
|
-
df to transpose.
|
|
871
|
-
header : str, default 'uid'
|
|
872
|
-
col used as the new header identifier.
|
|
873
|
-
|
|
874
|
-
Returns
|
|
875
|
-
-------
|
|
876
|
-
pandas.DataFrame
|
|
877
|
-
transposed df.
|
|
878
|
-
"""
|
|
879
|
-
|
|
880
|
-
cols_old = df.columns
|
|
881
|
-
cols_new = df[header].values
|
|
882
|
-
|
|
883
|
-
df = df.T
|
|
884
|
-
df.columns = cols_new
|
|
885
|
-
df.insert(0, header, cols_old)
|
|
886
|
-
df.drop(header, axis=0, inplace=True)
|
|
887
|
-
df.reset_index(drop=True, inplace=True)
|
|
888
|
-
|
|
889
|
-
return df
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
def split_col(
|
|
894
|
-
df: pd.DataFrame,
|
|
895
|
-
col: str = 'uid',
|
|
896
|
-
sep: str = '::\n',
|
|
897
|
-
new1: str = None,
|
|
898
|
-
new2: str = 'label',
|
|
899
|
-
):
|
|
900
|
-
|
|
901
|
-
if col not in df.columns:
|
|
902
|
-
log(f'ERROR: no col named "{col}" found')
|
|
903
|
-
return df
|
|
904
|
-
elif df.columns.tolist().count(col) > 1:
|
|
905
|
-
log(f'ERROR: multiple cols named "{col}" found')
|
|
906
|
-
return df
|
|
907
|
-
|
|
908
|
-
df = df.copy()
|
|
909
|
-
pos = df.columns.get_loc(col)
|
|
910
|
-
splits = df[col].str.partition(sep)
|
|
911
|
-
|
|
912
|
-
df.drop(columns=[col], inplace=True)
|
|
913
|
-
if new1 is None:
|
|
914
|
-
new1 = col
|
|
915
|
-
elif new1 in df.columns:
|
|
916
|
-
log(f'WARNING: Col {new1} already exists, choosing new name.')
|
|
917
|
-
new1 = ensure_unique_string(new1, df.columns.tolist())
|
|
918
|
-
if new2 in df.columns:
|
|
919
|
-
log(f'WARNING: Col {new2} already exists, choosing new name.')
|
|
920
|
-
new2 = ensure_unique_string(new2, df.columns.tolist() + [new1])
|
|
921
|
-
|
|
922
|
-
df.insert(pos, new1, splits[0]) #type: ignore
|
|
923
|
-
df.insert(pos + 1, new2, splits[2]) #type: ignore
|
|
924
|
-
|
|
925
|
-
return df
|
|
@@ -821,7 +821,7 @@ class TagMetadata(Symbol):
|
|
|
821
821
|
add a tag about the currently
|
|
822
822
|
selected rows into the metadata col.
|
|
823
823
|
|
|
824
|
-
assumes the metadata col is named "
|
|
824
|
+
assumes the metadata col is named "meta"
|
|
825
825
|
and creates it if it doesn't exist.
|
|
826
826
|
|
|
827
827
|
Examples
|
|
@@ -860,7 +860,7 @@ class TagMetadata(Symbol):
|
|
|
860
860
|
if len(self.args) == 2:
|
|
861
861
|
col_meta = str(self.args[1])
|
|
862
862
|
else:
|
|
863
|
-
col_meta = '
|
|
863
|
+
col_meta = 'meta'
|
|
864
864
|
|
|
865
865
|
if col_meta not in q.df.columns:
|
|
866
866
|
q = _insert_col_meta(col_meta, q)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|