tablelab 0.5.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.
- tablelab/__init__.py +72 -0
- tablelab/__main__.py +64 -0
- tablelab/app.py +431 -0
- tablelab/charts.py +242 -0
- tablelab/code_export.py +545 -0
- tablelab/column_inspector.py +303 -0
- tablelab/figures.py +101 -0
- tablelab/file_readers.py +129 -0
- tablelab/filepicker.py +195 -0
- tablelab/helpers/__init__.py +50 -0
- tablelab/helpers/data.py +30 -0
- tablelab/helpers/plotting.py +31 -0
- tablelab/helpers/widgets.py +5 -0
- tablelab/missing_values.py +97 -0
- tablelab/paged_preview.py +152 -0
- tablelab/pipeline.py +202 -0
- tablelab/query_app.py +1343 -0
- tablelab/query_expressions.py +91 -0
- tablelab/table.py +128 -0
- tablelab-0.5.0.dist-info/METADATA +465 -0
- tablelab-0.5.0.dist-info/RECORD +24 -0
- tablelab-0.5.0.dist-info/WHEEL +4 -0
- tablelab-0.5.0.dist-info/entry_points.txt +2 -0
- tablelab-0.5.0.dist-info/licenses/LICENSE +21 -0
tablelab/pipeline.py
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""Saved query settings, execution, and standalone pandas code for pipelines."""
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
from .missing_values import missing_result, missing_code
|
|
5
|
+
from .query_expressions import arithmetic_code, arithmetic_value, condition_tree, combine_code, combine_masks
|
|
6
|
+
|
|
7
|
+
OPERATIONS = ('Select', 'Join', 'Filter', 'Group & summarise', 'Missing values')
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def group_summary_name(keys, aggregate, column, alias=''):
|
|
11
|
+
alias = alias.strip()
|
|
12
|
+
if alias:
|
|
13
|
+
if alias in keys:
|
|
14
|
+
raise ValueError('Summary alias must differ from grouping column names.')
|
|
15
|
+
return alias
|
|
16
|
+
name = 'row_count' if aggregate == 'size' else f'{column}_{aggregate}'
|
|
17
|
+
while name in keys:
|
|
18
|
+
name += '_'
|
|
19
|
+
return name
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def capture_step(app):
|
|
23
|
+
operation = app.tabs.selected_index
|
|
24
|
+
step = {'operation': operation}
|
|
25
|
+
if operation == 0:
|
|
26
|
+
columns, order = app._select_settings()
|
|
27
|
+
step.update(columns=list(app.select_columns.value), order=order,
|
|
28
|
+
calculations=[(name, expression) for name, expression, _ in app._calculations()],
|
|
29
|
+
aliases=app._aliases(columns), distinct=app.select_distinct.value,
|
|
30
|
+
ascending=app.select_ascending.value, limit=app.select_limit.value,
|
|
31
|
+
offset=app.select_offset.value)
|
|
32
|
+
elif operation == 1:
|
|
33
|
+
if app.right.value is None:
|
|
34
|
+
raise ValueError('Select a right table.')
|
|
35
|
+
step.update(right=app.tables[app.right.value], left_keys=list(app.left_keys.value),
|
|
36
|
+
right_keys=list(app.right_keys.value), how=app.join_type.value)
|
|
37
|
+
elif operation == 2:
|
|
38
|
+
conditions, _ = app._conditions()
|
|
39
|
+
step.update(conditions=conditions, mode=app.filter_combine.value,
|
|
40
|
+
grouping=app.filter_expression.value if app.filter_combine.value == 'custom' else '')
|
|
41
|
+
elif operation == 3:
|
|
42
|
+
step.update(keys=list(app.group_key_order.options), aggregate=app.aggregate.value,
|
|
43
|
+
column=app.filter_column.value, alias=app.group_alias.value.strip())
|
|
44
|
+
elif operation == 4:
|
|
45
|
+
step.update(columns=list(app.missing_columns.value), action=app.missing_action.value,
|
|
46
|
+
value=app.missing_value.value, how=app.missing_how.value)
|
|
47
|
+
else:
|
|
48
|
+
raise ValueError('Select an operation tab.')
|
|
49
|
+
return step
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def require_columns(df, columns):
|
|
53
|
+
missing = [column for column in columns if column not in df.columns]
|
|
54
|
+
if missing:
|
|
55
|
+
raise ValueError('Columns are no longer available: ' + ', '.join(repr(column) for column in missing)
|
|
56
|
+
+ '. Edit this step to choose available columns.')
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def validate_step(df, step):
|
|
60
|
+
operation = step['operation']
|
|
61
|
+
if operation == 0:
|
|
62
|
+
available = list(df.columns)
|
|
63
|
+
for name, expression in step['calculations']:
|
|
64
|
+
if name in available:
|
|
65
|
+
raise ValueError(f'Calculated column already exists: {name!r}')
|
|
66
|
+
arithmetic_code(expression, available)
|
|
67
|
+
available.append(name)
|
|
68
|
+
require_columns(df, step['columns'])
|
|
69
|
+
output = step['columns'] + [name for name, _ in step['calculations']]
|
|
70
|
+
if not output:
|
|
71
|
+
raise ValueError('Select at least one output column.')
|
|
72
|
+
if any(column not in output for column in step['order']):
|
|
73
|
+
raise ValueError('Order by columns must also be selected as output columns.')
|
|
74
|
+
if any(column not in output for column in step['aliases']):
|
|
75
|
+
raise ValueError('Alias columns must be included in the output.')
|
|
76
|
+
names = [step['aliases'].get(column, column) for column in output]
|
|
77
|
+
if len(set(names)) != len(names):
|
|
78
|
+
raise ValueError('Output column names must be unique; choose different aliases.')
|
|
79
|
+
elif operation == 1:
|
|
80
|
+
if not step['left_keys'] or len(step['left_keys']) != len(step['right_keys']):
|
|
81
|
+
raise ValueError('Select the same number of matching keys on both sides.')
|
|
82
|
+
require_columns(df, step['left_keys'])
|
|
83
|
+
require_columns(step['right']['df'], step['right_keys'])
|
|
84
|
+
elif operation == 2:
|
|
85
|
+
require_columns(df, [column for column, _, _ in step['conditions']])
|
|
86
|
+
condition_tree(step['grouping'], len(step['conditions']), step['mode'])
|
|
87
|
+
elif operation == 3:
|
|
88
|
+
group_summary_name(step['keys'], step['aggregate'], step['column'], step.get('alias', ''))
|
|
89
|
+
require_columns(df, step['keys'])
|
|
90
|
+
if step['aggregate'] != 'size':
|
|
91
|
+
require_columns(df, [step['column']])
|
|
92
|
+
elif operation == 4:
|
|
93
|
+
require_columns(df, step['columns'])
|
|
94
|
+
else:
|
|
95
|
+
raise ValueError('Unknown pipeline operation.')
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def execute_step(df, step):
|
|
99
|
+
# Imported lazily because the widget app also uses these pipeline functions.
|
|
100
|
+
from .query_app import filter_mask, group_table
|
|
101
|
+
validate_step(df, step)
|
|
102
|
+
operation = step['operation']
|
|
103
|
+
if operation == 0:
|
|
104
|
+
source = df.copy() if step['calculations'] else df
|
|
105
|
+
for name, expression in step['calculations']:
|
|
106
|
+
source[name] = arithmetic_value(expression, source)
|
|
107
|
+
columns = step['columns'] + [name for name, _ in step['calculations']]
|
|
108
|
+
result = source.loc[:, columns].copy()
|
|
109
|
+
if step['distinct']:
|
|
110
|
+
result = result.drop_duplicates()
|
|
111
|
+
if step['order']:
|
|
112
|
+
result = result.sort_values(step['order'], ascending=step['ascending'],
|
|
113
|
+
kind='stable', na_position='last')
|
|
114
|
+
start = step['offset']
|
|
115
|
+
stop = start + step['limit'] if step['limit'] else None
|
|
116
|
+
return result.iloc[start:stop].copy().rename(columns=step['aliases'])
|
|
117
|
+
if operation == 1:
|
|
118
|
+
return df.merge(step['right']['df'], left_on=step['left_keys'], right_on=step['right_keys'],
|
|
119
|
+
how=step['how'], suffixes=('_left', '_right'))
|
|
120
|
+
if operation == 2:
|
|
121
|
+
masks = [filter_mask(df, column, operator, value) for column, operator, value in step['conditions']]
|
|
122
|
+
tree = condition_tree(step['grouping'], len(masks), step['mode'])
|
|
123
|
+
return df.loc[combine_masks(tree, masks)].copy()
|
|
124
|
+
if operation == 3:
|
|
125
|
+
return group_table(df, step['keys'], step['aggregate'], step['column'], step.get('alias', ''))
|
|
126
|
+
return missing_result(df, step['columns'], step['action'], step['value'], step['how'])
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def filter_code_lines(df, column, operator, value):
|
|
130
|
+
lines = [f'series = source[{column!r}]']
|
|
131
|
+
if operator in {'missing', 'present'}:
|
|
132
|
+
mask = 'series.isna()' if operator == 'missing' else 'series.notna()'
|
|
133
|
+
elif operator == 'contains':
|
|
134
|
+
mask = f"series.astype('string').str.contains({value!r}, regex=False, na=False)"
|
|
135
|
+
else:
|
|
136
|
+
dtype = df[column].dtype
|
|
137
|
+
expression = repr(value)
|
|
138
|
+
if pd.api.types.is_bool_dtype(dtype):
|
|
139
|
+
choices = {'true': True, 'false': False, '1': True, '0': False}
|
|
140
|
+
if value.strip().lower() not in choices:
|
|
141
|
+
raise ValueError('For Boolean columns, enter true or false.')
|
|
142
|
+
expression = repr(choices[value.strip().lower()])
|
|
143
|
+
elif pd.api.types.is_numeric_dtype(dtype):
|
|
144
|
+
expression = f'pd.to_numeric({value!r})'
|
|
145
|
+
elif pd.api.types.is_datetime64_any_dtype(dtype):
|
|
146
|
+
expression = f'pd.to_datetime({value!r})'
|
|
147
|
+
elif pd.api.types.is_timedelta64_dtype(dtype):
|
|
148
|
+
expression = f'pd.to_timedelta({value!r})'
|
|
149
|
+
lines.append(f'value = {expression}')
|
|
150
|
+
mask = f'series.{operator}(value)'
|
|
151
|
+
lines.append(f'masks.append(({mask}).fillna(False))')
|
|
152
|
+
return lines
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def step_code(df, step):
|
|
156
|
+
"""Code assumes `source` is the input and defines `result` as the output."""
|
|
157
|
+
validate_step(df, step)
|
|
158
|
+
operation = step['operation']
|
|
159
|
+
if operation == 0:
|
|
160
|
+
lines = []
|
|
161
|
+
available = list(df.columns)
|
|
162
|
+
if step['calculations']:
|
|
163
|
+
# A shallow copy is enough to add columns without copying the existing data.
|
|
164
|
+
lines.append('source = source.copy(deep=False)')
|
|
165
|
+
for name, expression in step['calculations']:
|
|
166
|
+
lines.append(f'source[{name!r}] = {arithmetic_code(expression, available)}')
|
|
167
|
+
available.append(name)
|
|
168
|
+
columns = step['columns'] + [name for name, _ in step['calculations']]
|
|
169
|
+
lines.append(f'result = source.loc[:, {columns!r}].copy()')
|
|
170
|
+
if step['distinct']:
|
|
171
|
+
lines.append('result = result.drop_duplicates()')
|
|
172
|
+
if step['order']:
|
|
173
|
+
lines.append(f"result = result.sort_values({step['order']!r}, ascending={step['ascending']!r}, "
|
|
174
|
+
"kind='stable', na_position='last')")
|
|
175
|
+
if step['offset'] or step['limit']:
|
|
176
|
+
start = step['offset']
|
|
177
|
+
stop = start + step['limit'] if step['limit'] else ''
|
|
178
|
+
lines.append(f'result = result.iloc[{start}:{stop}].copy()')
|
|
179
|
+
if step['aliases']:
|
|
180
|
+
lines.append(f"result = result.rename(columns={step['aliases']!r})")
|
|
181
|
+
return lines
|
|
182
|
+
if operation == 1:
|
|
183
|
+
return ['result = source.merge(', f" right, left_on={step['left_keys']!r},",
|
|
184
|
+
f" right_on={step['right_keys']!r}, how={step['how']!r},",
|
|
185
|
+
" suffixes=('_left', '_right'),", ')']
|
|
186
|
+
if operation == 2:
|
|
187
|
+
lines = ['masks = []']
|
|
188
|
+
for column, operator, value in step['conditions']:
|
|
189
|
+
lines.extend(filter_code_lines(df, column, operator, value))
|
|
190
|
+
tree = condition_tree(step['grouping'], len(step['conditions']), step['mode'])
|
|
191
|
+
return lines + [f'mask = {combine_code(tree)}', 'result = source.loc[mask].copy()']
|
|
192
|
+
if operation == 3:
|
|
193
|
+
keys, aggregate, column = step['keys'], step['aggregate'], step['column']
|
|
194
|
+
if not keys:
|
|
195
|
+
raise ValueError('Select at least one grouping column.')
|
|
196
|
+
if aggregate != 'size' and aggregate != 'count' and not pd.api.types.is_numeric_dtype(df[column].dtype):
|
|
197
|
+
raise ValueError('This summary requires a numeric value column.')
|
|
198
|
+
name = group_summary_name(keys, aggregate, column, step.get('alias', ''))
|
|
199
|
+
expression = 'grouped.size()' if aggregate == 'size' else f'grouped[{column!r}].agg({aggregate!r})'
|
|
200
|
+
return [f'grouped = source.groupby({keys!r}, dropna=False, observed=True, sort=False)',
|
|
201
|
+
f'result = {expression}.reset_index(name={name!r})']
|
|
202
|
+
return missing_code(df, step['columns'], step['action'], step['value'], step['how'])
|