perspective-python 4.2.0__cp311-abi3-win_amd64.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.
- perspective/__init__.py +396 -0
- perspective/extension/finos-perspective-nbextension.json +5 -0
- perspective/handlers/__init__.py +11 -0
- perspective/handlers/aiohttp.py +61 -0
- perspective/handlers/starlette.py +55 -0
- perspective/handlers/tornado.py +184 -0
- perspective/perspective.pyd +0 -0
- perspective/templates/exported_widget.html.template +35 -0
- perspective/tests/__init__.py +11 -0
- perspective/tests/async/test_async_client.py +83 -0
- perspective/tests/async/test_websocket_client.py +124 -0
- perspective/tests/conftest.py +272 -0
- perspective/tests/core/__init__.py +11 -0
- perspective/tests/core/test_async.py +351 -0
- perspective/tests/multi_threaded/__init__.py +11 -0
- perspective/tests/multi_threaded/test_multi_threaded.py +201 -0
- perspective/tests/server/__init__.py +11 -0
- perspective/tests/server/test_server.py +1016 -0
- perspective/tests/server/test_session.py +110 -0
- perspective/tests/table/__init__.py +11 -0
- perspective/tests/table/arrow/date32.arrow +0 -0
- perspective/tests/table/arrow/date64.arrow +0 -0
- perspective/tests/table/arrow/dict.arrow +0 -0
- perspective/tests/table/arrow/dict_update.arrow +0 -0
- perspective/tests/table/arrow/int_float_str.arrow +0 -0
- perspective/tests/table/arrow/int_float_str_file.arrow +0 -0
- perspective/tests/table/arrow/int_float_str_update.arrow +0 -0
- perspective/tests/table/object_sequence.py +402 -0
- perspective/tests/table/test_column_paths.py +89 -0
- perspective/tests/table/test_delete.py +124 -0
- perspective/tests/table/test_exception.py +65 -0
- perspective/tests/table/test_leaks.py +54 -0
- perspective/tests/table/test_ports.py +178 -0
- perspective/tests/table/test_remove.py +102 -0
- perspective/tests/table/test_table.py +641 -0
- perspective/tests/table/test_table_arrow.py +503 -0
- perspective/tests/table/test_table_datetime.py +2409 -0
- perspective/tests/table/test_table_infer.py +201 -0
- perspective/tests/table/test_table_limit.py +45 -0
- perspective/tests/table/test_table_numpy.py +1022 -0
- perspective/tests/table/test_table_pandas.py +1018 -0
- perspective/tests/table/test_table_polars.py +251 -0
- perspective/tests/table/test_table_view_table.py +130 -0
- perspective/tests/table/test_to_arrow.py +417 -0
- perspective/tests/table/test_to_arrow_lz4.py +32 -0
- perspective/tests/table/test_to_format.py +1024 -0
- perspective/tests/table/test_to_polars.py +26 -0
- perspective/tests/table/test_update.py +545 -0
- perspective/tests/table/test_update_arrow.py +980 -0
- perspective/tests/table/test_update_pandas.py +211 -0
- perspective/tests/table/test_view.py +2261 -0
- perspective/tests/table/test_view_expression.py +1940 -0
- perspective/tests/test_dependencies.py +53 -0
- perspective/tests/viewer/__init__.py +11 -0
- perspective/tests/viewer/test_viewer.py +246 -0
- perspective/tests/widget/__init__.py +11 -0
- perspective/tests/widget/test_widget.py +278 -0
- perspective/tests/widget/test_widget_pandas.py +453 -0
- perspective/virtual_servers/__init__.py +134 -0
- perspective/virtual_servers/clickhouse.py +245 -0
- perspective/virtual_servers/duckdb.py +236 -0
- perspective/widget/__init__.py +349 -0
- perspective/widget/viewer/__init__.py +15 -0
- perspective/widget/viewer/validate.py +22 -0
- perspective/widget/viewer/viewer.py +343 -0
- perspective/widget/viewer/viewer_traitlets.py +101 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/install.json +5 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/package.json +71 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/253.5f5c9e80605aa4106a28.js +2 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/253.5f5c9e80605aa4106a28.js.LICENSE.txt +25 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/523.c030af5d3c4f67ff83f6.js +1 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/remoteEntry.95a8ea1b44d96032833f.js +1 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/style.js +4 -0
- perspective_python-4.2.0.data/data/share/jupyter/labextensions/@perspective-dev/jupyterlab/static/third-party-licenses.json +16 -0
- perspective_python-4.2.0.dist-info/METADATA +27 -0
- perspective_python-4.2.0.dist-info/RECORD +79 -0
- perspective_python-4.2.0.dist-info/WHEEL +4 -0
- perspective_python-4.2.0.dist-info/licenses/LICENSE.md +193 -0
- perspective_python-4.2.0.dist-info/licenses/LICENSE_THIRDPARTY_cargo.yml +17395 -0
|
@@ -0,0 +1,2261 @@
|
|
|
1
|
+
# ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
# ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
# ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
# ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
# ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
# ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
# ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
# ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
# ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
# ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
# ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import pandas as pd
|
|
14
|
+
import numpy as np
|
|
15
|
+
from perspective import PerspectiveError
|
|
16
|
+
from datetime import date, datetime
|
|
17
|
+
from pytest import approx, mark, raises
|
|
18
|
+
|
|
19
|
+
import perspective as psp
|
|
20
|
+
|
|
21
|
+
client = psp.Server().new_local_client()
|
|
22
|
+
Table = client.table
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def date_timestamp(date):
|
|
26
|
+
return int(datetime.combine(date, datetime.min.time()).timestamp()) * 1000
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def compare_delta(received, expected):
|
|
30
|
+
"""Compare an arrow-serialized row delta by constructing a Table."""
|
|
31
|
+
tbl = Table(received)
|
|
32
|
+
assert tbl.view().to_columns() == expected
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TestView(object):
|
|
36
|
+
def test_view_zero(self):
|
|
37
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
38
|
+
tbl = Table(data)
|
|
39
|
+
view = tbl.view()
|
|
40
|
+
dimms = view.dimensions()
|
|
41
|
+
assert dimms["num_view_rows"] == 2
|
|
42
|
+
assert dimms["num_view_columns"] == 2
|
|
43
|
+
assert view.schema() == {"a": "integer", "b": "integer"}
|
|
44
|
+
assert view.to_records() == data
|
|
45
|
+
|
|
46
|
+
def test_view_one(self):
|
|
47
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
48
|
+
tbl = Table(data)
|
|
49
|
+
view = tbl.view(group_by=["a"])
|
|
50
|
+
dimms = view.dimensions()
|
|
51
|
+
assert dimms["num_view_rows"] == 3
|
|
52
|
+
assert dimms["num_view_columns"] == 2
|
|
53
|
+
assert view.schema() == {"a": "integer", "b": "integer"}
|
|
54
|
+
assert view.to_records() == [
|
|
55
|
+
{"__ROW_PATH__": [], "a": 4, "b": 6},
|
|
56
|
+
{"__ROW_PATH__": [1], "a": 1, "b": 2},
|
|
57
|
+
{"__ROW_PATH__": [3], "a": 3, "b": 4},
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
def test_view_two(self):
|
|
61
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
62
|
+
tbl = Table(data)
|
|
63
|
+
view = tbl.view(group_by=["a"], split_by=["b"])
|
|
64
|
+
dimms = view.dimensions()
|
|
65
|
+
assert dimms["num_view_rows"] == 3
|
|
66
|
+
assert dimms["num_view_columns"] == 4
|
|
67
|
+
assert view.schema() == {"a": "integer", "b": "integer"}
|
|
68
|
+
assert view.to_records() == [
|
|
69
|
+
{"2|a": 1, "2|b": 2, "4|a": 3, "4|b": 4, "__ROW_PATH__": []},
|
|
70
|
+
{"2|a": 1, "2|b": 2, "4|a": None, "4|b": None, "__ROW_PATH__": [1]},
|
|
71
|
+
{"2|a": None, "2|b": None, "4|a": 3, "4|b": 4, "__ROW_PATH__": [3]},
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
def test_view_two_column_only(self):
|
|
75
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
76
|
+
tbl = Table(data)
|
|
77
|
+
view = tbl.view(split_by=["b"])
|
|
78
|
+
dimms = view.dimensions()
|
|
79
|
+
assert dimms["num_view_rows"] == 2
|
|
80
|
+
assert dimms["num_view_columns"] == 4
|
|
81
|
+
assert view.schema() == {"a": "integer", "b": "integer"}
|
|
82
|
+
assert view.to_records() == [
|
|
83
|
+
{"2|a": 1, "2|b": 2, "4|a": None, "4|b": None},
|
|
84
|
+
{"2|a": None, "2|b": None, "4|a": 3, "4|b": 4},
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
# column path
|
|
88
|
+
|
|
89
|
+
def test_view_column_path_zero(self):
|
|
90
|
+
data = {"a": [1, 2, 3], "b": [1.5, 2.5, 3.5]}
|
|
91
|
+
tbl = Table(data)
|
|
92
|
+
view = tbl.view()
|
|
93
|
+
paths = view.column_paths()
|
|
94
|
+
assert paths == ["a", "b"]
|
|
95
|
+
|
|
96
|
+
def test_view_column_path_zero_schema(self):
|
|
97
|
+
data = {"a": "integer", "b": "float"}
|
|
98
|
+
tbl = Table(data)
|
|
99
|
+
view = tbl.view()
|
|
100
|
+
paths = view.column_paths()
|
|
101
|
+
assert paths == ["a", "b"]
|
|
102
|
+
|
|
103
|
+
def test_view_column_path_zero_hidden(self):
|
|
104
|
+
data = {"a": [1, 2, 3], "b": [1.5, 2.5, 3.5]}
|
|
105
|
+
tbl = Table(data)
|
|
106
|
+
view = tbl.view(columns=["b"])
|
|
107
|
+
paths = view.column_paths()
|
|
108
|
+
assert paths == ["b"]
|
|
109
|
+
|
|
110
|
+
def test_view_column_path_zero_respects_order(self):
|
|
111
|
+
data = {"a": [1, 2, 3], "b": [1.5, 2.5, 3.5]}
|
|
112
|
+
tbl = Table(data)
|
|
113
|
+
view = tbl.view(columns=["b", "a"])
|
|
114
|
+
paths = view.column_paths()
|
|
115
|
+
assert paths == ["b", "a"]
|
|
116
|
+
|
|
117
|
+
def test_view_column_path_one(self):
|
|
118
|
+
data = {"a": [1, 2, 3], "b": [1.5, 2.5, 3.5]}
|
|
119
|
+
tbl = Table(data)
|
|
120
|
+
view = tbl.view(group_by=["a"])
|
|
121
|
+
paths = view.column_paths()
|
|
122
|
+
assert paths == ["a", "b"]
|
|
123
|
+
|
|
124
|
+
def test_view_column_path_one_numeric_names(self):
|
|
125
|
+
data = {"a": [1, 2, 3], "b": [1.5, 2.5, 3.5], "1234": [5, 6, 7]}
|
|
126
|
+
tbl = Table(data)
|
|
127
|
+
view = tbl.view(group_by=["a"], columns=["b", "1234", "a"])
|
|
128
|
+
paths = view.column_paths()
|
|
129
|
+
assert paths == ["b", "1234", "a"]
|
|
130
|
+
|
|
131
|
+
def test_view_column_path_two(self):
|
|
132
|
+
data = {"a": [1, 2, 3], "b": [1.5, 2.5, 3.5]}
|
|
133
|
+
tbl = Table(data)
|
|
134
|
+
view = tbl.view(group_by=["a"], split_by=["b"])
|
|
135
|
+
paths = view.column_paths()
|
|
136
|
+
assert paths == [
|
|
137
|
+
"1.5|a",
|
|
138
|
+
"1.5|b",
|
|
139
|
+
"2.5|a",
|
|
140
|
+
"2.5|b",
|
|
141
|
+
"3.5|a",
|
|
142
|
+
"3.5|b",
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
def test_view_column_path_two_column_only(self):
|
|
146
|
+
data = {"a": [1, 2, 3], "b": [1.5, 2.5, 3.5]}
|
|
147
|
+
tbl = Table(data)
|
|
148
|
+
view = tbl.view(split_by=["b"])
|
|
149
|
+
paths = view.column_paths()
|
|
150
|
+
assert paths == ["1.5|a", "1.5|b", "2.5|a", "2.5|b", "3.5|a", "3.5|b"]
|
|
151
|
+
|
|
152
|
+
def test_view_column_path_hidden_sort(self):
|
|
153
|
+
data = {"a": [1, 2, 3], "b": [1.5, 2.5, 3.5], "c": [3, 2, 1]}
|
|
154
|
+
tbl = Table(data)
|
|
155
|
+
view = tbl.view(columns=["a", "b"], sort=[["c", "desc"]])
|
|
156
|
+
paths = view.column_paths()
|
|
157
|
+
assert paths == ["a", "b"]
|
|
158
|
+
|
|
159
|
+
def test_view_column_path_hidden_col_sort(self):
|
|
160
|
+
data = {"a": [1, 2, 3], "b": [1.5, 2.5, 3.5], "c": [3, 2, 1]}
|
|
161
|
+
tbl = Table(data)
|
|
162
|
+
view = tbl.view(split_by=["a"], columns=["a", "b"], sort=[["c", "col desc"]])
|
|
163
|
+
paths = view.column_paths()
|
|
164
|
+
assert paths == ["1|a", "1|b", "2|a", "2|b", "3|a", "3|b"]
|
|
165
|
+
|
|
166
|
+
def test_view_column_path_pivot_by_bool(self):
|
|
167
|
+
data = {"a": [1, 2, 3], "b": [True, False, True], "c": [3, 2, 1]}
|
|
168
|
+
tbl = Table(data)
|
|
169
|
+
view = tbl.view(split_by=["b"], columns=["a", "b", "c"])
|
|
170
|
+
paths = view.column_paths()
|
|
171
|
+
assert paths == ["false|a", "false|b", "false|c", "true|a", "true|b", "true|c"]
|
|
172
|
+
|
|
173
|
+
# schema correctness
|
|
174
|
+
|
|
175
|
+
def test_string_view_schema(self):
|
|
176
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
177
|
+
tbl = Table(data)
|
|
178
|
+
view = tbl.view()
|
|
179
|
+
assert view.schema() == {"a": "integer", "b": "integer"}
|
|
180
|
+
|
|
181
|
+
def test_zero_view_schema(self):
|
|
182
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
183
|
+
tbl = Table(data)
|
|
184
|
+
view = tbl.view()
|
|
185
|
+
assert view.schema() == {"a": "integer", "b": "integer"}
|
|
186
|
+
|
|
187
|
+
def test_one_view_schema(self):
|
|
188
|
+
data = [{"a": "abc", "b": 2}, {"a": "abc", "b": 4}]
|
|
189
|
+
tbl = Table(data)
|
|
190
|
+
view = tbl.view(group_by=["a"], aggregates={"a": "distinct count"})
|
|
191
|
+
assert view.schema() == {"a": "integer", "b": "integer"}
|
|
192
|
+
|
|
193
|
+
def test_two_view_schema(self):
|
|
194
|
+
data = [{"a": "abc", "b": "def"}, {"a": "abc", "b": "def"}]
|
|
195
|
+
tbl = Table(data)
|
|
196
|
+
view = tbl.view(
|
|
197
|
+
group_by=["a"], split_by=["b"], aggregates={"a": "count", "b": "count"}
|
|
198
|
+
)
|
|
199
|
+
assert view.schema() == {"a": "integer", "b": "integer"}
|
|
200
|
+
|
|
201
|
+
# aggregates and column specification
|
|
202
|
+
|
|
203
|
+
def test_view_no_columns(self):
|
|
204
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
205
|
+
tbl = Table(data)
|
|
206
|
+
view = tbl.view(columns=[])
|
|
207
|
+
assert view.dimensions()["num_view_columns"] == 0
|
|
208
|
+
assert view.to_records() == []
|
|
209
|
+
|
|
210
|
+
def test_view_no_columns_pivoted(self):
|
|
211
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
212
|
+
tbl = Table(data)
|
|
213
|
+
view = tbl.view(group_by=["a"], columns=[])
|
|
214
|
+
assert view.dimensions()["num_view_columns"] == 0
|
|
215
|
+
assert view.to_records() == [
|
|
216
|
+
{"__ROW_PATH__": []},
|
|
217
|
+
{"__ROW_PATH__": [1]},
|
|
218
|
+
{"__ROW_PATH__": [3]},
|
|
219
|
+
]
|
|
220
|
+
|
|
221
|
+
def test_view_specific_column(self):
|
|
222
|
+
data = [{"a": 1, "b": 2, "c": 3, "d": 4}, {"a": 3, "b": 4, "c": 5, "d": 6}]
|
|
223
|
+
tbl = Table(data)
|
|
224
|
+
view = tbl.view(columns=["a", "c", "d"])
|
|
225
|
+
assert view.dimensions()["num_view_columns"] == 3
|
|
226
|
+
assert view.to_records() == [{"a": 1, "c": 3, "d": 4}, {"a": 3, "c": 5, "d": 6}]
|
|
227
|
+
|
|
228
|
+
def test_view_column_order(self):
|
|
229
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
230
|
+
tbl = Table(data)
|
|
231
|
+
view = tbl.view(columns=["b", "a"])
|
|
232
|
+
assert view.to_records() == [{"b": 2, "a": 1}, {"b": 4, "a": 3}]
|
|
233
|
+
|
|
234
|
+
def test_view_dataframe_column_order(self):
|
|
235
|
+
table = Table(
|
|
236
|
+
pd.DataFrame(
|
|
237
|
+
{
|
|
238
|
+
"0.1": [5, 6, 7, 8],
|
|
239
|
+
"-0.05": [5, 6, 7, 8],
|
|
240
|
+
"0.0": [1, 2, 3, 4],
|
|
241
|
+
"-0.1": [1, 2, 3, 4],
|
|
242
|
+
"str": ["a", "b", "c", "d"],
|
|
243
|
+
}
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
view = table.view(columns=["-0.1", "-0.05", "0.0", "0.1"], group_by=["str"])
|
|
247
|
+
assert view.column_paths() == ["-0.1", "-0.05", "0.0", "0.1"]
|
|
248
|
+
|
|
249
|
+
def test_view_aggregate_order_with_columns(self):
|
|
250
|
+
"""If `columns` is provided, order is always guaranteed."""
|
|
251
|
+
data = [{"a": 1, "b": 2, "c": 3, "d": 4}, {"a": 3, "b": 4, "c": 5, "d": 6}]
|
|
252
|
+
tbl = Table(data)
|
|
253
|
+
view = tbl.view(
|
|
254
|
+
group_by=["a"],
|
|
255
|
+
columns=["a", "b", "c", "d"],
|
|
256
|
+
aggregates={"d": "avg", "c": "avg", "b": "last", "a": "last"},
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
order = ["a", "b", "c", "d"]
|
|
260
|
+
assert view.column_paths() == order
|
|
261
|
+
|
|
262
|
+
def test_view_df_aggregate_order_with_columns(self):
|
|
263
|
+
"""If `columns` is provided, order is always guaranteed."""
|
|
264
|
+
data = pd.DataFrame(
|
|
265
|
+
{"a": [1, 2, 3], "b": [2, 3, 4], "c": [3, 4, 5], "d": [4, 5, 6]},
|
|
266
|
+
columns=["d", "a", "c", "b"],
|
|
267
|
+
)
|
|
268
|
+
tbl = Table(data)
|
|
269
|
+
view = tbl.view(
|
|
270
|
+
group_by=["a"],
|
|
271
|
+
aggregates={"d": "avg", "c": "avg", "b": "last", "a": "last"},
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
order = ["index", "d", "a", "c", "b"]
|
|
275
|
+
assert view.column_paths() == order
|
|
276
|
+
|
|
277
|
+
def test_view_aggregates_with_no_columns(self):
|
|
278
|
+
data = [{"a": 1, "b": 2, "c": 3, "d": 4}, {"a": 3, "b": 4, "c": 5, "d": 6}]
|
|
279
|
+
tbl = Table(data)
|
|
280
|
+
view = tbl.view(
|
|
281
|
+
group_by=["a"], aggregates={"c": "avg", "a": "last"}, columns=[]
|
|
282
|
+
)
|
|
283
|
+
assert view.column_paths() == []
|
|
284
|
+
assert view.to_records() == [
|
|
285
|
+
{"__ROW_PATH__": []},
|
|
286
|
+
{"__ROW_PATH__": [1]},
|
|
287
|
+
{"__ROW_PATH__": [3]},
|
|
288
|
+
]
|
|
289
|
+
|
|
290
|
+
def test_view_aggregates_default_column_order(self):
|
|
291
|
+
"""Order of columns are entirely determined by the `columns` kwarg. If
|
|
292
|
+
it is not provided, order of columns is default based on the order
|
|
293
|
+
of table.columns()."""
|
|
294
|
+
data = [{"a": 1, "b": 2, "c": 3, "d": 4}, {"a": 3, "b": 4, "c": 5, "d": 6}]
|
|
295
|
+
tbl = Table(data)
|
|
296
|
+
cols = tbl.columns()
|
|
297
|
+
view = tbl.view(group_by=["a"], aggregates={"c": "avg", "a": "last"})
|
|
298
|
+
|
|
299
|
+
assert view.column_paths() == cols
|
|
300
|
+
|
|
301
|
+
# check that default aggregates have been applied
|
|
302
|
+
result = view.to_columns()
|
|
303
|
+
assert result["b"] == [6, 2, 4]
|
|
304
|
+
assert result["d"] == [10, 4, 6]
|
|
305
|
+
|
|
306
|
+
# and that specified aggregates are applied
|
|
307
|
+
assert result["a"] == [3, 1, 3]
|
|
308
|
+
assert result["c"] == [4, 3, 5]
|
|
309
|
+
|
|
310
|
+
# row and split by paths
|
|
311
|
+
def test_view_group_by_datetime_row_paths_are_same_as_data(self, util):
|
|
312
|
+
"""Tests row paths for datetimes in UTC. Timezone-related tests are
|
|
313
|
+
in the `test_table_datetime` file."""
|
|
314
|
+
data = {"a": [datetime(2019, 7, 11, 12, 30)], "b": [1]}
|
|
315
|
+
tbl = Table(data)
|
|
316
|
+
view = tbl.view(group_by=["a"])
|
|
317
|
+
data = view.to_columns()
|
|
318
|
+
|
|
319
|
+
for rp in data["__ROW_PATH__"]:
|
|
320
|
+
if len(rp) > 0:
|
|
321
|
+
assert rp[0] == util.to_timestamp(datetime(2019, 7, 11, 12, 30))
|
|
322
|
+
|
|
323
|
+
assert tbl.view().to_columns() == {
|
|
324
|
+
"a": [util.to_timestamp(datetime(2019, 7, 11, 12, 30))],
|
|
325
|
+
"b": [1],
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
def test_view_split_by_datetime_names_utc(self):
|
|
329
|
+
"""Tests column paths for datetimes in UTC. Timezone-related tests are
|
|
330
|
+
in the `test_table_datetime` file."""
|
|
331
|
+
data = {"a": [datetime(2019, 7, 11, 12, 30)], "b": [1]}
|
|
332
|
+
tbl = Table(data)
|
|
333
|
+
view = tbl.view(split_by=["a"])
|
|
334
|
+
cols = view.column_paths()
|
|
335
|
+
assert cols == ["2019-07-11 12:30:00.000|a", "2019-07-11 12:30:00.000|b"]
|
|
336
|
+
|
|
337
|
+
# TODO: time slightly off! thinks its NYE 1969
|
|
338
|
+
@mark.skip # We do not support python datetimes.
|
|
339
|
+
def test_view_split_by_datetime_names_min(self):
|
|
340
|
+
"""Tests column paths for datetimes in UTC. Timezone-related tests are
|
|
341
|
+
in the `test_table_datetime` file."""
|
|
342
|
+
import os
|
|
343
|
+
|
|
344
|
+
os.environ["TZ"] = "UTC"
|
|
345
|
+
data = {"a": [datetime.min], "b": [1]}
|
|
346
|
+
tbl = Table({"a": "datetime", "b": "integer"})
|
|
347
|
+
tbl.update(data)
|
|
348
|
+
view = tbl.view(split_by=["a"])
|
|
349
|
+
cols = view.column_paths()
|
|
350
|
+
assert cols == ["1970-01-01 00:00:00.000|a", "1970-01-01 00:00:00.000|b"]
|
|
351
|
+
|
|
352
|
+
@mark.skip # We dont support python datetimes.
|
|
353
|
+
def test_view_split_by_datetime_names_max(self):
|
|
354
|
+
"""Tests column paths for datetimes in UTC. Timezone-related tests are
|
|
355
|
+
in the `test_table_datetime` file."""
|
|
356
|
+
data = {"a": [datetime.max], "b": [1]}
|
|
357
|
+
tbl = Table(data)
|
|
358
|
+
view = tbl.view(split_by=["a"])
|
|
359
|
+
cols = view.column_paths()
|
|
360
|
+
assert cols == ["10000-01-01 00:00:00.000|a", "10000-01-01 00:00:00.000|b"]
|
|
361
|
+
|
|
362
|
+
# aggregate
|
|
363
|
+
|
|
364
|
+
def test_view_aggregate_int(self):
|
|
365
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
366
|
+
tbl = Table(data)
|
|
367
|
+
view = tbl.view(aggregates={"a": "avg"}, group_by=["a"])
|
|
368
|
+
assert view.to_records() == [
|
|
369
|
+
{"__ROW_PATH__": [], "a": 2.0, "b": 6},
|
|
370
|
+
{"__ROW_PATH__": [1], "a": 1.0, "b": 2},
|
|
371
|
+
{"__ROW_PATH__": [3], "a": 3.0, "b": 4},
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
def test_view_aggregate_str(self):
|
|
375
|
+
data = [{"a": "abc", "b": 2}, {"a": "def", "b": 4}]
|
|
376
|
+
tbl = Table(data)
|
|
377
|
+
view = tbl.view(aggregates={"a": "count"}, group_by=["a"])
|
|
378
|
+
assert view.to_records() == [
|
|
379
|
+
{"__ROW_PATH__": [], "a": 2, "b": 6},
|
|
380
|
+
{"__ROW_PATH__": ["abc"], "a": 1, "b": 2},
|
|
381
|
+
{"__ROW_PATH__": ["def"], "a": 1, "b": 4},
|
|
382
|
+
]
|
|
383
|
+
|
|
384
|
+
def test_view_aggregate_datetime(self, util):
|
|
385
|
+
data = [
|
|
386
|
+
{"a": datetime(2019, 10, 1, 11, 30)},
|
|
387
|
+
{"a": datetime(2019, 10, 1, 11, 30)},
|
|
388
|
+
]
|
|
389
|
+
tbl = Table(data)
|
|
390
|
+
view = tbl.view(aggregates={"a": "distinct count"}, group_by=["a"])
|
|
391
|
+
assert view.to_records() == [
|
|
392
|
+
{"__ROW_PATH__": [], "a": 1},
|
|
393
|
+
{
|
|
394
|
+
"__ROW_PATH__": [util.to_timestamp(datetime(2019, 10, 1, 11, 30))],
|
|
395
|
+
"a": 1,
|
|
396
|
+
},
|
|
397
|
+
]
|
|
398
|
+
|
|
399
|
+
def test_view_aggregate_datetime_leading_zeroes(self, util):
|
|
400
|
+
data = [
|
|
401
|
+
{"a": datetime(2019, 1, 1, 5, 5, 5)},
|
|
402
|
+
{"a": datetime(2019, 1, 1, 5, 5, 5)},
|
|
403
|
+
]
|
|
404
|
+
tbl = Table(data)
|
|
405
|
+
view = tbl.view(aggregates={"a": "distinct count"}, group_by=["a"])
|
|
406
|
+
assert view.to_records() == [
|
|
407
|
+
{"__ROW_PATH__": [], "a": 1},
|
|
408
|
+
{
|
|
409
|
+
"__ROW_PATH__": [util.to_timestamp(datetime(2019, 1, 1, 5, 5, 5))],
|
|
410
|
+
"a": 1,
|
|
411
|
+
},
|
|
412
|
+
]
|
|
413
|
+
|
|
414
|
+
def test_view_aggregate_mean(self):
|
|
415
|
+
data = [
|
|
416
|
+
{"a": "a", "x": 1, "y": 200},
|
|
417
|
+
{"a": "a", "x": 2, "y": 100},
|
|
418
|
+
{"a": "a", "x": 3, "y": None},
|
|
419
|
+
]
|
|
420
|
+
tbl = Table(data)
|
|
421
|
+
view = tbl.view(aggregates={"y": "mean"}, group_by=["a"], columns=["y"])
|
|
422
|
+
assert view.to_records() == [
|
|
423
|
+
{"__ROW_PATH__": [], "y": 300 / 2},
|
|
424
|
+
{"__ROW_PATH__": ["a"], "y": 300 / 2},
|
|
425
|
+
]
|
|
426
|
+
|
|
427
|
+
def test_view_aggregate_mean_from_schema(self):
|
|
428
|
+
data = [
|
|
429
|
+
{"a": "a", "x": 1, "y": 200},
|
|
430
|
+
{"a": "a", "x": 2, "y": 100},
|
|
431
|
+
{"a": "a", "x": 3, "y": None},
|
|
432
|
+
]
|
|
433
|
+
tbl = Table({"a": "string", "x": "integer", "y": "float"})
|
|
434
|
+
view = tbl.view(aggregates={"y": "mean"}, group_by=["a"], columns=["y"])
|
|
435
|
+
tbl.update(data)
|
|
436
|
+
assert view.to_records() == [
|
|
437
|
+
{"__ROW_PATH__": [], "y": 300 / 2},
|
|
438
|
+
{"__ROW_PATH__": ["a"], "y": 300 / 2},
|
|
439
|
+
]
|
|
440
|
+
|
|
441
|
+
def test_view_aggregate_weighted_mean(self):
|
|
442
|
+
data = [
|
|
443
|
+
{"a": "a", "x": 1, "y": 200},
|
|
444
|
+
{"a": "a", "x": 2, "y": 100},
|
|
445
|
+
{"a": "a", "x": 3, "y": None},
|
|
446
|
+
]
|
|
447
|
+
tbl = Table(data)
|
|
448
|
+
view = tbl.view(
|
|
449
|
+
aggregates={"y": ("weighted mean", ["x"])},
|
|
450
|
+
group_by=["a"],
|
|
451
|
+
columns=["y"],
|
|
452
|
+
)
|
|
453
|
+
assert view.to_records() == [
|
|
454
|
+
{"__ROW_PATH__": [], "y": (1.0 * 200 + 2 * 100) / (1.0 + 2)},
|
|
455
|
+
{"__ROW_PATH__": ["a"], "y": (1.0 * 200 + 2 * 100) / (1.0 + 2)},
|
|
456
|
+
]
|
|
457
|
+
|
|
458
|
+
def test_view_aggregate_weighted_mean_with_negative_weights(self):
|
|
459
|
+
data = [
|
|
460
|
+
{"a": "a", "x": 1, "y": 200},
|
|
461
|
+
{"a": "a", "x": -2, "y": 100},
|
|
462
|
+
{"a": "a", "x": 3, "y": None},
|
|
463
|
+
]
|
|
464
|
+
tbl = Table(data)
|
|
465
|
+
view = tbl.view(
|
|
466
|
+
aggregates={"y": ("weighted mean", ["x"])},
|
|
467
|
+
group_by=["a"],
|
|
468
|
+
columns=["y"],
|
|
469
|
+
)
|
|
470
|
+
assert view.to_records() == [
|
|
471
|
+
{"__ROW_PATH__": [], "y": (1 * 200 + (-2) * 100) / (1 - 2)},
|
|
472
|
+
{"__ROW_PATH__": ["a"], "y": (1 * 200 + (-2) * 100) / (1 - 2)},
|
|
473
|
+
]
|
|
474
|
+
|
|
475
|
+
def test_view_variance(self):
|
|
476
|
+
data = {"x": list(np.random.rand(10)), "y": ["a" for _ in range(10)]}
|
|
477
|
+
|
|
478
|
+
table = Table(data)
|
|
479
|
+
view = table.view(aggregates={"x": "var"}, group_by=["y"])
|
|
480
|
+
|
|
481
|
+
result = view.to_columns()
|
|
482
|
+
expected = np.var(data["x"])
|
|
483
|
+
|
|
484
|
+
assert result["x"] == approx([expected, expected])
|
|
485
|
+
|
|
486
|
+
def test_view_variance_multi(self):
|
|
487
|
+
data = {
|
|
488
|
+
"a": [
|
|
489
|
+
91.96,
|
|
490
|
+
258.576,
|
|
491
|
+
29.6,
|
|
492
|
+
243.16,
|
|
493
|
+
36.24,
|
|
494
|
+
25.248,
|
|
495
|
+
79.99,
|
|
496
|
+
206.1,
|
|
497
|
+
31.5,
|
|
498
|
+
55.6,
|
|
499
|
+
],
|
|
500
|
+
"b": [1 if i % 2 == 0 else 0 for i in range(10)],
|
|
501
|
+
}
|
|
502
|
+
table = Table(data)
|
|
503
|
+
view = table.view(aggregates={"a": "var"}, group_by=["b"])
|
|
504
|
+
|
|
505
|
+
result = view.to_columns()
|
|
506
|
+
expected_total = np.var(data["a"])
|
|
507
|
+
expected_zero = np.var(
|
|
508
|
+
[data["a"][1], data["a"][3], data["a"][5], data["a"][7], data["a"][9]]
|
|
509
|
+
)
|
|
510
|
+
expected_one = np.var(
|
|
511
|
+
[data["a"][0], data["a"][2], data["a"][4], data["a"][6], data["a"][8]]
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
assert result["a"] == approx([expected_total, expected_zero, expected_one])
|
|
515
|
+
|
|
516
|
+
def test_view_variance_update_none(self):
|
|
517
|
+
data = {"a": [0.1, 0.5, None, 0.8], "b": [0, 1, 0, 1], "c": [1, 2, 3, 4]}
|
|
518
|
+
table = Table(data, index="c")
|
|
519
|
+
view = table.view(columns=["a"], group_by=["b"], aggregates={"a": "var"})
|
|
520
|
+
result = view.to_columns()
|
|
521
|
+
assert result["a"][0] == approx(np.var([0.1, 0.5, 0.8]))
|
|
522
|
+
assert result["a"][1] is None
|
|
523
|
+
assert result["a"][2] == approx(np.var([0.5, 0.8]))
|
|
524
|
+
|
|
525
|
+
table.update({"a": [0.3], "c": [3]})
|
|
526
|
+
|
|
527
|
+
result = view.to_columns()
|
|
528
|
+
assert result["a"] == approx(
|
|
529
|
+
[np.var([0.1, 0.5, 0.3, 0.8]), np.var([0.1, 0.3]), np.var([0.5, 0.8])]
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
table.update({"a": [None], "c": [1]})
|
|
533
|
+
|
|
534
|
+
result = view.to_columns()
|
|
535
|
+
assert result["a"][0] == approx(np.var([0.5, 0.3, 0.8]))
|
|
536
|
+
assert result["a"][1] is None
|
|
537
|
+
assert result["a"][2] == approx(np.var([0.5, 0.8]))
|
|
538
|
+
|
|
539
|
+
def test_view_variance_multi_update(self):
|
|
540
|
+
data = {
|
|
541
|
+
"a": [
|
|
542
|
+
91.96,
|
|
543
|
+
258.576,
|
|
544
|
+
29.6,
|
|
545
|
+
243.16,
|
|
546
|
+
36.24,
|
|
547
|
+
25.248,
|
|
548
|
+
79.99,
|
|
549
|
+
206.1,
|
|
550
|
+
31.5,
|
|
551
|
+
55.6,
|
|
552
|
+
],
|
|
553
|
+
"b": [1 if i % 2 == 0 else 0 for i in range(10)],
|
|
554
|
+
}
|
|
555
|
+
table = Table(data)
|
|
556
|
+
view = table.view(aggregates={"a": "var"}, group_by=["b"])
|
|
557
|
+
|
|
558
|
+
result = view.to_columns()
|
|
559
|
+
expected_total = data["a"]
|
|
560
|
+
expected_zero = [
|
|
561
|
+
data["a"][1],
|
|
562
|
+
data["a"][3],
|
|
563
|
+
data["a"][5],
|
|
564
|
+
data["a"][7],
|
|
565
|
+
data["a"][9],
|
|
566
|
+
]
|
|
567
|
+
expected_one = [
|
|
568
|
+
data["a"][0],
|
|
569
|
+
data["a"][2],
|
|
570
|
+
data["a"][4],
|
|
571
|
+
data["a"][6],
|
|
572
|
+
data["a"][8],
|
|
573
|
+
]
|
|
574
|
+
|
|
575
|
+
assert result["a"] == approx(
|
|
576
|
+
[np.var(expected_total), np.var(expected_zero), np.var(expected_one)]
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
# 2 here should result in null var because the group size is 1
|
|
580
|
+
update_data = {"a": [15.12, 9.102, 0.99, 12.8], "b": [1, 0, 1, 2]}
|
|
581
|
+
table.update(update_data)
|
|
582
|
+
|
|
583
|
+
result = view.to_columns()
|
|
584
|
+
expected_total += update_data["a"]
|
|
585
|
+
expected_zero += [update_data["a"][1]]
|
|
586
|
+
expected_one += [update_data["a"][0], update_data["a"][2]]
|
|
587
|
+
|
|
588
|
+
assert result["__ROW_PATH__"] == [[], [0], [1], [2]]
|
|
589
|
+
assert result["a"][:-1] == approx(
|
|
590
|
+
[np.var(expected_total), np.var(expected_zero), np.var(expected_one)]
|
|
591
|
+
)
|
|
592
|
+
assert result["a"][-1] is None
|
|
593
|
+
|
|
594
|
+
def test_view_variance_multi_update_delta(self):
|
|
595
|
+
data = {
|
|
596
|
+
"a": [
|
|
597
|
+
91.96,
|
|
598
|
+
258.576,
|
|
599
|
+
29.6,
|
|
600
|
+
243.16,
|
|
601
|
+
36.24,
|
|
602
|
+
25.248,
|
|
603
|
+
79.99,
|
|
604
|
+
206.1,
|
|
605
|
+
31.5,
|
|
606
|
+
55.6,
|
|
607
|
+
],
|
|
608
|
+
"b": [1 if i % 2 == 0 else 0 for i in range(10)],
|
|
609
|
+
}
|
|
610
|
+
table = Table(data)
|
|
611
|
+
view = table.view(aggregates={"a": "var"}, group_by=["b"])
|
|
612
|
+
|
|
613
|
+
result = view.to_columns()
|
|
614
|
+
expected_total = data["a"]
|
|
615
|
+
expected_zero = [
|
|
616
|
+
data["a"][1],
|
|
617
|
+
data["a"][3],
|
|
618
|
+
data["a"][5],
|
|
619
|
+
data["a"][7],
|
|
620
|
+
data["a"][9],
|
|
621
|
+
]
|
|
622
|
+
expected_one = [
|
|
623
|
+
data["a"][0],
|
|
624
|
+
data["a"][2],
|
|
625
|
+
data["a"][4],
|
|
626
|
+
data["a"][6],
|
|
627
|
+
data["a"][8],
|
|
628
|
+
]
|
|
629
|
+
|
|
630
|
+
assert result["a"] == approx(
|
|
631
|
+
[np.var(expected_total), np.var(expected_zero), np.var(expected_one)]
|
|
632
|
+
)
|
|
633
|
+
|
|
634
|
+
# 2 here should result in null var because the group size is 1
|
|
635
|
+
update_data = {"a": [15.12, 9.102, 0.99, 12.8], "b": [1, 0, 1, 2]}
|
|
636
|
+
|
|
637
|
+
def cb1(port_id, delta):
|
|
638
|
+
table2 = Table(delta)
|
|
639
|
+
view2 = table2.view()
|
|
640
|
+
result = view2.to_columns()
|
|
641
|
+
|
|
642
|
+
flat_view = table.view()
|
|
643
|
+
flat_data = flat_view.to_columns()
|
|
644
|
+
result = view.to_columns()
|
|
645
|
+
|
|
646
|
+
expected_total = flat_data["a"]
|
|
647
|
+
expected_zero = []
|
|
648
|
+
expected_one = []
|
|
649
|
+
|
|
650
|
+
for i, num in enumerate(expected_total):
|
|
651
|
+
if flat_data["b"][i] == 1:
|
|
652
|
+
expected_one.append(num)
|
|
653
|
+
elif flat_data["b"][i] == 0:
|
|
654
|
+
expected_zero.append(num)
|
|
655
|
+
|
|
656
|
+
assert result["a"][0] == approx(np.var(expected_total))
|
|
657
|
+
assert result["a"][1] == approx(np.var(expected_zero))
|
|
658
|
+
assert result["a"][2] == approx(np.var(expected_one))
|
|
659
|
+
assert result["a"][3] is None
|
|
660
|
+
|
|
661
|
+
view.on_update(cb1, mode="row")
|
|
662
|
+
|
|
663
|
+
table.update(update_data)
|
|
664
|
+
|
|
665
|
+
def test_view_variance_multi_update_indexed(self):
|
|
666
|
+
data = {
|
|
667
|
+
"a": [
|
|
668
|
+
91.96,
|
|
669
|
+
258.576,
|
|
670
|
+
29.6,
|
|
671
|
+
243.16,
|
|
672
|
+
36.24,
|
|
673
|
+
25.248,
|
|
674
|
+
79.99,
|
|
675
|
+
206.1,
|
|
676
|
+
31.5,
|
|
677
|
+
55.6,
|
|
678
|
+
],
|
|
679
|
+
"b": [1 if i % 2 == 0 else 0 for i in range(10)],
|
|
680
|
+
"c": [i for i in range(10)],
|
|
681
|
+
}
|
|
682
|
+
table = Table(data, index="c")
|
|
683
|
+
view = table.view(aggregates={"a": "var"}, group_by=["b"])
|
|
684
|
+
|
|
685
|
+
result = view.to_columns()
|
|
686
|
+
expected_total = data["a"]
|
|
687
|
+
expected_zero = [
|
|
688
|
+
data["a"][1],
|
|
689
|
+
data["a"][3],
|
|
690
|
+
data["a"][5],
|
|
691
|
+
data["a"][7],
|
|
692
|
+
data["a"][9],
|
|
693
|
+
]
|
|
694
|
+
expected_one = [
|
|
695
|
+
data["a"][0],
|
|
696
|
+
data["a"][2],
|
|
697
|
+
data["a"][4],
|
|
698
|
+
data["a"][6],
|
|
699
|
+
data["a"][8],
|
|
700
|
+
]
|
|
701
|
+
|
|
702
|
+
assert result["a"] == approx(
|
|
703
|
+
[np.var(expected_total), np.var(expected_zero), np.var(expected_one)]
|
|
704
|
+
)
|
|
705
|
+
|
|
706
|
+
# "b" = 2 here should result in null var because the group size is 1
|
|
707
|
+
update_data = {
|
|
708
|
+
"a": [15.12, 9.102, 0.99, 12.8],
|
|
709
|
+
"b": [1, 0, 1, 2],
|
|
710
|
+
"c": [1, 5, 2, 7],
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
table.update(update_data)
|
|
714
|
+
|
|
715
|
+
result = view.to_columns()
|
|
716
|
+
|
|
717
|
+
view2 = table.view()
|
|
718
|
+
flat_data = view2.to_columns()
|
|
719
|
+
expected_total = flat_data["a"]
|
|
720
|
+
|
|
721
|
+
expected_zero = []
|
|
722
|
+
expected_one = []
|
|
723
|
+
|
|
724
|
+
for i, val in enumerate(flat_data["a"]):
|
|
725
|
+
if flat_data["b"][i] == 1:
|
|
726
|
+
expected_one.append(val)
|
|
727
|
+
elif flat_data["b"][i] == 0:
|
|
728
|
+
expected_zero.append(val)
|
|
729
|
+
|
|
730
|
+
assert result["__ROW_PATH__"] == [[], [0], [1], [2]]
|
|
731
|
+
assert result["a"][:-1] == approx(
|
|
732
|
+
[np.var(expected_total), np.var(expected_zero), np.var(expected_one)]
|
|
733
|
+
)
|
|
734
|
+
assert result["a"][-1] is None
|
|
735
|
+
|
|
736
|
+
def test_view_variance_multi_update_indexed_delta(self):
|
|
737
|
+
data = {
|
|
738
|
+
"a": [
|
|
739
|
+
91.96,
|
|
740
|
+
258.576,
|
|
741
|
+
29.6,
|
|
742
|
+
243.16,
|
|
743
|
+
36.24,
|
|
744
|
+
25.248,
|
|
745
|
+
79.99,
|
|
746
|
+
206.1,
|
|
747
|
+
31.5,
|
|
748
|
+
55.6,
|
|
749
|
+
],
|
|
750
|
+
"b": [1 if i % 2 == 0 else 0 for i in range(10)],
|
|
751
|
+
"c": [i for i in range(10)],
|
|
752
|
+
}
|
|
753
|
+
table = Table(data, index="c")
|
|
754
|
+
view = table.view(
|
|
755
|
+
aggregates={"a": "var", "b": "last", "c": "last"}, group_by=["b"]
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
result = view.to_columns()
|
|
759
|
+
expected_total = data["a"]
|
|
760
|
+
expected_zero = [
|
|
761
|
+
data["a"][1],
|
|
762
|
+
data["a"][3],
|
|
763
|
+
data["a"][5],
|
|
764
|
+
data["a"][7],
|
|
765
|
+
data["a"][9],
|
|
766
|
+
]
|
|
767
|
+
expected_one = [
|
|
768
|
+
data["a"][0],
|
|
769
|
+
data["a"][2],
|
|
770
|
+
data["a"][4],
|
|
771
|
+
data["a"][6],
|
|
772
|
+
data["a"][8],
|
|
773
|
+
]
|
|
774
|
+
|
|
775
|
+
assert result["a"] == approx(
|
|
776
|
+
[np.var(expected_total), np.var(expected_zero), np.var(expected_one)]
|
|
777
|
+
)
|
|
778
|
+
|
|
779
|
+
# 2 here should result in null var because the group size is 1
|
|
780
|
+
update_data = {
|
|
781
|
+
"a": [15.12, 9.102, 0.99, 12.8],
|
|
782
|
+
"b": [1, 0, 1, 2],
|
|
783
|
+
"c": [0, 4, 1, 6],
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
def cb1(port_id, delta):
|
|
787
|
+
table2 = Table(delta)
|
|
788
|
+
view2 = table2.view()
|
|
789
|
+
result = view2.to_columns()
|
|
790
|
+
|
|
791
|
+
flat_view = table.view()
|
|
792
|
+
flat_result = flat_view.to_columns()
|
|
793
|
+
|
|
794
|
+
new_a = flat_result["a"]
|
|
795
|
+
b = flat_result["b"]
|
|
796
|
+
expected_zero = []
|
|
797
|
+
expected_one = []
|
|
798
|
+
|
|
799
|
+
for i, num in enumerate(new_a):
|
|
800
|
+
if b[i] == 0:
|
|
801
|
+
expected_zero.append(num)
|
|
802
|
+
elif b[i] == 1:
|
|
803
|
+
expected_one.append(num)
|
|
804
|
+
|
|
805
|
+
assert result["a"][0] == approx(np.var(new_a))
|
|
806
|
+
assert result["a"][1] == approx(np.var(expected_zero))
|
|
807
|
+
assert result["a"][2] == approx(np.var(expected_one))
|
|
808
|
+
assert result["a"][3] is None
|
|
809
|
+
assert result["b"] == [2, 0, 1, 2]
|
|
810
|
+
assert result["c"] == [6, 9, 8, 6]
|
|
811
|
+
|
|
812
|
+
view.on_update(cb1, mode="row")
|
|
813
|
+
|
|
814
|
+
table.update(update_data)
|
|
815
|
+
|
|
816
|
+
def test_view_variance_less_than_two(self):
|
|
817
|
+
data = {"a": list(np.random.rand(10)), "b": [i for i in range(10)]}
|
|
818
|
+
|
|
819
|
+
table = Table(data)
|
|
820
|
+
view = table.view(aggregates={"a": "var"}, group_by=["b"])
|
|
821
|
+
|
|
822
|
+
result = view.to_columns()
|
|
823
|
+
assert result["a"][0] == approx(np.var(data["a"]))
|
|
824
|
+
assert result["a"][1:] == [None] * 10
|
|
825
|
+
|
|
826
|
+
def test_view_variance_normal_distribution(self):
|
|
827
|
+
data = {"a": list(np.random.standard_normal(100)), "b": [1] * 100}
|
|
828
|
+
|
|
829
|
+
table = Table(data)
|
|
830
|
+
view = table.view(aggregates={"a": "var"}, group_by=["b"])
|
|
831
|
+
|
|
832
|
+
result = view.to_columns()
|
|
833
|
+
assert result["a"] == approx([np.var(data["a"]), np.var(data["a"])])
|
|
834
|
+
|
|
835
|
+
def test_view_standard_deviation(self):
|
|
836
|
+
data = {"x": list(np.random.rand(10)), "y": ["a" for _ in range(10)]}
|
|
837
|
+
|
|
838
|
+
table = Table(data)
|
|
839
|
+
view = table.view(aggregates={"x": "stddev"}, group_by=["y"])
|
|
840
|
+
|
|
841
|
+
result = view.to_columns()
|
|
842
|
+
expected = np.std(data["x"])
|
|
843
|
+
|
|
844
|
+
assert result["x"] == approx([expected, expected])
|
|
845
|
+
|
|
846
|
+
def test_view_standard_deviation_multi(self):
|
|
847
|
+
data = {
|
|
848
|
+
"a": [
|
|
849
|
+
91.96,
|
|
850
|
+
258.576,
|
|
851
|
+
29.6,
|
|
852
|
+
243.16,
|
|
853
|
+
36.24,
|
|
854
|
+
25.248,
|
|
855
|
+
79.99,
|
|
856
|
+
206.1,
|
|
857
|
+
31.5,
|
|
858
|
+
55.6,
|
|
859
|
+
],
|
|
860
|
+
"b": [1 if i % 2 == 0 else 0 for i in range(10)],
|
|
861
|
+
}
|
|
862
|
+
table = Table(data)
|
|
863
|
+
view = table.view(aggregates={"a": "stddev"}, group_by=["b"])
|
|
864
|
+
|
|
865
|
+
result = view.to_columns()
|
|
866
|
+
expected_total = np.std(data["a"])
|
|
867
|
+
expected_zero = np.std(
|
|
868
|
+
[data["a"][1], data["a"][3], data["a"][5], data["a"][7], data["a"][9]]
|
|
869
|
+
)
|
|
870
|
+
expected_one = np.std(
|
|
871
|
+
[data["a"][0], data["a"][2], data["a"][4], data["a"][6], data["a"][8]]
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
assert result["a"] == approx([expected_total, expected_zero, expected_one])
|
|
875
|
+
|
|
876
|
+
def test_view_standard_deviation_update_none(self):
|
|
877
|
+
data = {"a": [0.1, 0.5, None, 0.8], "b": [0, 1, 0, 1], "c": [1, 2, 3, 4]}
|
|
878
|
+
table = Table(data, index="c")
|
|
879
|
+
view = table.view(columns=["a"], group_by=["b"], aggregates={"a": "stddev"})
|
|
880
|
+
result = view.to_columns()
|
|
881
|
+
assert result["a"][0] == approx(np.std([0.1, 0.5, 0.8]))
|
|
882
|
+
assert result["a"][1] is None
|
|
883
|
+
assert result["a"][2] == approx(np.std([0.5, 0.8]))
|
|
884
|
+
|
|
885
|
+
table.update({"a": [0.3], "c": [3]})
|
|
886
|
+
|
|
887
|
+
result = view.to_columns()
|
|
888
|
+
assert result["a"] == approx(
|
|
889
|
+
[np.std([0.1, 0.5, 0.3, 0.8]), np.std([0.1, 0.3]), np.std([0.5, 0.8])]
|
|
890
|
+
)
|
|
891
|
+
|
|
892
|
+
table.update({"a": [None], "c": [1]})
|
|
893
|
+
|
|
894
|
+
result = view.to_columns()
|
|
895
|
+
assert result["a"][0] == approx(np.std([0.5, 0.3, 0.8]))
|
|
896
|
+
assert result["a"][1] is None
|
|
897
|
+
assert result["a"][2] == approx(np.std([0.5, 0.8]))
|
|
898
|
+
|
|
899
|
+
def test_view_standard_deviation_multi_update(self):
|
|
900
|
+
data = {
|
|
901
|
+
"a": [
|
|
902
|
+
91.96,
|
|
903
|
+
258.576,
|
|
904
|
+
29.6,
|
|
905
|
+
243.16,
|
|
906
|
+
36.24,
|
|
907
|
+
25.248,
|
|
908
|
+
79.99,
|
|
909
|
+
206.1,
|
|
910
|
+
31.5,
|
|
911
|
+
55.6,
|
|
912
|
+
],
|
|
913
|
+
"b": [1 if i % 2 == 0 else 0 for i in range(10)],
|
|
914
|
+
}
|
|
915
|
+
table = Table(data)
|
|
916
|
+
view = table.view(aggregates={"a": "stddev"}, group_by=["b"])
|
|
917
|
+
|
|
918
|
+
result = view.to_columns()
|
|
919
|
+
expected_total = data["a"]
|
|
920
|
+
expected_zero = [
|
|
921
|
+
data["a"][1],
|
|
922
|
+
data["a"][3],
|
|
923
|
+
data["a"][5],
|
|
924
|
+
data["a"][7],
|
|
925
|
+
data["a"][9],
|
|
926
|
+
]
|
|
927
|
+
expected_one = [
|
|
928
|
+
data["a"][0],
|
|
929
|
+
data["a"][2],
|
|
930
|
+
data["a"][4],
|
|
931
|
+
data["a"][6],
|
|
932
|
+
data["a"][8],
|
|
933
|
+
]
|
|
934
|
+
|
|
935
|
+
assert result["a"] == approx(
|
|
936
|
+
[np.std(expected_total), np.std(expected_zero), np.std(expected_one)]
|
|
937
|
+
)
|
|
938
|
+
|
|
939
|
+
# 2 here should result in null stddev because the group size is 1
|
|
940
|
+
update_data = {"a": [15.12, 9.102, 0.99, 12.8], "b": [1, 0, 1, 2]}
|
|
941
|
+
table.update(update_data)
|
|
942
|
+
|
|
943
|
+
result = view.to_columns()
|
|
944
|
+
expected_total += update_data["a"]
|
|
945
|
+
expected_zero += [update_data["a"][1]]
|
|
946
|
+
expected_one += [update_data["a"][0], update_data["a"][2]]
|
|
947
|
+
|
|
948
|
+
assert result["__ROW_PATH__"] == [[], [0], [1], [2]]
|
|
949
|
+
assert result["a"][:-1] == approx(
|
|
950
|
+
[np.std(expected_total), np.std(expected_zero), np.std(expected_one)]
|
|
951
|
+
)
|
|
952
|
+
assert result["a"][-1] is None
|
|
953
|
+
|
|
954
|
+
def test_view_standard_deviation_multi_update_delta(self):
|
|
955
|
+
data = {
|
|
956
|
+
"a": [
|
|
957
|
+
91.96,
|
|
958
|
+
258.576,
|
|
959
|
+
29.6,
|
|
960
|
+
243.16,
|
|
961
|
+
36.24,
|
|
962
|
+
25.248,
|
|
963
|
+
79.99,
|
|
964
|
+
206.1,
|
|
965
|
+
31.5,
|
|
966
|
+
55.6,
|
|
967
|
+
],
|
|
968
|
+
"b": [1 if i % 2 == 0 else 0 for i in range(10)],
|
|
969
|
+
}
|
|
970
|
+
table = Table(data)
|
|
971
|
+
view = table.view(aggregates={"a": "stddev"}, group_by=["b"])
|
|
972
|
+
|
|
973
|
+
result = view.to_columns()
|
|
974
|
+
expected_total = data["a"]
|
|
975
|
+
expected_zero = [
|
|
976
|
+
data["a"][1],
|
|
977
|
+
data["a"][3],
|
|
978
|
+
data["a"][5],
|
|
979
|
+
data["a"][7],
|
|
980
|
+
data["a"][9],
|
|
981
|
+
]
|
|
982
|
+
expected_one = [
|
|
983
|
+
data["a"][0],
|
|
984
|
+
data["a"][2],
|
|
985
|
+
data["a"][4],
|
|
986
|
+
data["a"][6],
|
|
987
|
+
data["a"][8],
|
|
988
|
+
]
|
|
989
|
+
|
|
990
|
+
assert result["a"] == approx(
|
|
991
|
+
[np.std(expected_total), np.std(expected_zero), np.std(expected_one)]
|
|
992
|
+
)
|
|
993
|
+
|
|
994
|
+
# 2 here should result in null stddev because the group size is 1
|
|
995
|
+
update_data = {"a": [15.12, 9.102, 0.99, 12.8], "b": [1, 0, 1, 2]}
|
|
996
|
+
|
|
997
|
+
def cb1(port_id, delta):
|
|
998
|
+
table2 = Table(delta)
|
|
999
|
+
view2 = table2.view()
|
|
1000
|
+
result = view2.to_columns()
|
|
1001
|
+
|
|
1002
|
+
flat_view = table.view()
|
|
1003
|
+
flat_data = flat_view.to_columns()
|
|
1004
|
+
result = view.to_columns()
|
|
1005
|
+
|
|
1006
|
+
expected_total = flat_data["a"]
|
|
1007
|
+
expected_zero = []
|
|
1008
|
+
expected_one = []
|
|
1009
|
+
|
|
1010
|
+
for i, num in enumerate(expected_total):
|
|
1011
|
+
if flat_data["b"][i] == 1:
|
|
1012
|
+
expected_one.append(num)
|
|
1013
|
+
elif flat_data["b"][i] == 0:
|
|
1014
|
+
expected_zero.append(num)
|
|
1015
|
+
|
|
1016
|
+
assert result["a"][0] == approx(np.std(expected_total))
|
|
1017
|
+
assert result["a"][1] == approx(np.std(expected_zero))
|
|
1018
|
+
assert result["a"][2] == approx(np.std(expected_one))
|
|
1019
|
+
assert result["a"][3] is None
|
|
1020
|
+
|
|
1021
|
+
view.on_update(cb1, mode="row")
|
|
1022
|
+
|
|
1023
|
+
table.update(update_data)
|
|
1024
|
+
|
|
1025
|
+
def test_view_standard_deviation_multi_update_indexed(self):
|
|
1026
|
+
data = {
|
|
1027
|
+
"a": [
|
|
1028
|
+
91.96,
|
|
1029
|
+
258.576,
|
|
1030
|
+
29.6,
|
|
1031
|
+
243.16,
|
|
1032
|
+
36.24,
|
|
1033
|
+
25.248,
|
|
1034
|
+
79.99,
|
|
1035
|
+
206.1,
|
|
1036
|
+
31.5,
|
|
1037
|
+
55.6,
|
|
1038
|
+
],
|
|
1039
|
+
"b": [1 if i % 2 == 0 else 0 for i in range(10)],
|
|
1040
|
+
"c": [i for i in range(10)],
|
|
1041
|
+
}
|
|
1042
|
+
table = Table(data, index="c")
|
|
1043
|
+
view = table.view(aggregates={"a": "stddev"}, group_by=["b"])
|
|
1044
|
+
|
|
1045
|
+
result = view.to_columns()
|
|
1046
|
+
expected_total = data["a"]
|
|
1047
|
+
expected_zero = [
|
|
1048
|
+
data["a"][1],
|
|
1049
|
+
data["a"][3],
|
|
1050
|
+
data["a"][5],
|
|
1051
|
+
data["a"][7],
|
|
1052
|
+
data["a"][9],
|
|
1053
|
+
]
|
|
1054
|
+
expected_one = [
|
|
1055
|
+
data["a"][0],
|
|
1056
|
+
data["a"][2],
|
|
1057
|
+
data["a"][4],
|
|
1058
|
+
data["a"][6],
|
|
1059
|
+
data["a"][8],
|
|
1060
|
+
]
|
|
1061
|
+
|
|
1062
|
+
assert result["a"] == approx(
|
|
1063
|
+
[np.std(expected_total), np.std(expected_zero), np.std(expected_one)]
|
|
1064
|
+
)
|
|
1065
|
+
|
|
1066
|
+
# "b" = 2 here should result in null stddev because the group size is 1
|
|
1067
|
+
update_data = {
|
|
1068
|
+
"a": [15.12, 9.102, 0.99, 12.8],
|
|
1069
|
+
"b": [1, 0, 1, 2],
|
|
1070
|
+
"c": [1, 5, 2, 7],
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
table.update(update_data)
|
|
1074
|
+
|
|
1075
|
+
result = view.to_columns()
|
|
1076
|
+
|
|
1077
|
+
view2 = table.view()
|
|
1078
|
+
flat_data = view2.to_columns()
|
|
1079
|
+
expected_total = flat_data["a"]
|
|
1080
|
+
|
|
1081
|
+
expected_zero = []
|
|
1082
|
+
expected_one = []
|
|
1083
|
+
|
|
1084
|
+
for i, val in enumerate(flat_data["a"]):
|
|
1085
|
+
if flat_data["b"][i] == 1:
|
|
1086
|
+
expected_one.append(val)
|
|
1087
|
+
elif flat_data["b"][i] == 0:
|
|
1088
|
+
expected_zero.append(val)
|
|
1089
|
+
|
|
1090
|
+
assert result["__ROW_PATH__"] == [[], [0], [1], [2]]
|
|
1091
|
+
assert result["a"][:-1] == approx(
|
|
1092
|
+
[np.std(expected_total), np.std(expected_zero), np.std(expected_one)]
|
|
1093
|
+
)
|
|
1094
|
+
assert result["a"][-1] is None
|
|
1095
|
+
|
|
1096
|
+
def test_view_standard_deviation_multi_update_indexed_delta(self):
|
|
1097
|
+
data = {
|
|
1098
|
+
"a": [
|
|
1099
|
+
91.96,
|
|
1100
|
+
258.576,
|
|
1101
|
+
29.6,
|
|
1102
|
+
243.16,
|
|
1103
|
+
36.24,
|
|
1104
|
+
25.248,
|
|
1105
|
+
79.99,
|
|
1106
|
+
206.1,
|
|
1107
|
+
31.5,
|
|
1108
|
+
55.6,
|
|
1109
|
+
],
|
|
1110
|
+
"b": [1 if i % 2 == 0 else 0 for i in range(10)],
|
|
1111
|
+
"c": [i for i in range(10)],
|
|
1112
|
+
}
|
|
1113
|
+
table = Table(data, index="c")
|
|
1114
|
+
view = table.view(
|
|
1115
|
+
aggregates={"a": "stddev", "b": "last", "c": "last"}, group_by=["b"]
|
|
1116
|
+
)
|
|
1117
|
+
|
|
1118
|
+
result = view.to_columns()
|
|
1119
|
+
expected_total = data["a"]
|
|
1120
|
+
expected_zero = [
|
|
1121
|
+
data["a"][1],
|
|
1122
|
+
data["a"][3],
|
|
1123
|
+
data["a"][5],
|
|
1124
|
+
data["a"][7],
|
|
1125
|
+
data["a"][9],
|
|
1126
|
+
]
|
|
1127
|
+
expected_one = [
|
|
1128
|
+
data["a"][0],
|
|
1129
|
+
data["a"][2],
|
|
1130
|
+
data["a"][4],
|
|
1131
|
+
data["a"][6],
|
|
1132
|
+
data["a"][8],
|
|
1133
|
+
]
|
|
1134
|
+
|
|
1135
|
+
assert result["a"] == approx(
|
|
1136
|
+
[np.std(expected_total), np.std(expected_zero), np.std(expected_one)]
|
|
1137
|
+
)
|
|
1138
|
+
|
|
1139
|
+
# 2 here should result in null stddev because the group size is 1
|
|
1140
|
+
update_data = {
|
|
1141
|
+
"a": [15.12, 9.102, 0.99, 12.8],
|
|
1142
|
+
"b": [1, 0, 1, 2],
|
|
1143
|
+
"c": [0, 4, 1, 6],
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
def cb1(port_id, delta):
|
|
1147
|
+
table2 = Table(delta)
|
|
1148
|
+
view2 = table2.view()
|
|
1149
|
+
result = view2.to_columns()
|
|
1150
|
+
|
|
1151
|
+
flat_view = table.view()
|
|
1152
|
+
flat_result = flat_view.to_columns()
|
|
1153
|
+
|
|
1154
|
+
new_a = flat_result["a"]
|
|
1155
|
+
b = flat_result["b"]
|
|
1156
|
+
expected_zero = []
|
|
1157
|
+
expected_one = []
|
|
1158
|
+
|
|
1159
|
+
for i, num in enumerate(new_a):
|
|
1160
|
+
if b[i] == 0:
|
|
1161
|
+
expected_zero.append(num)
|
|
1162
|
+
elif b[i] == 1:
|
|
1163
|
+
expected_one.append(num)
|
|
1164
|
+
|
|
1165
|
+
assert result["a"][0] == approx(np.std(new_a))
|
|
1166
|
+
assert result["a"][1] == approx(np.std(expected_zero))
|
|
1167
|
+
assert result["a"][2] == approx(np.std(expected_one))
|
|
1168
|
+
assert result["a"][3] is None
|
|
1169
|
+
assert result["b"] == [2, 0, 1, 2]
|
|
1170
|
+
assert result["c"] == [6, 9, 8, 6]
|
|
1171
|
+
|
|
1172
|
+
view.on_update(cb1, mode="row")
|
|
1173
|
+
|
|
1174
|
+
table.update(update_data)
|
|
1175
|
+
|
|
1176
|
+
def test_view_standard_deviation_less_than_two(self):
|
|
1177
|
+
data = {"a": list(np.random.rand(10)), "b": [i for i in range(10)]}
|
|
1178
|
+
|
|
1179
|
+
table = Table(data)
|
|
1180
|
+
view = table.view(aggregates={"a": "stddev"}, group_by=["b"])
|
|
1181
|
+
|
|
1182
|
+
result = view.to_columns()
|
|
1183
|
+
assert result["a"][0] == approx(np.std(data["a"]))
|
|
1184
|
+
assert result["a"][1:] == [None] * 10
|
|
1185
|
+
|
|
1186
|
+
def test_view_standard_deviation_normal_distribution(self):
|
|
1187
|
+
data = {"a": list(np.random.standard_normal(100)), "b": [1] * 100}
|
|
1188
|
+
|
|
1189
|
+
table = Table(data)
|
|
1190
|
+
view = table.view(aggregates={"a": "stddev"}, group_by=["b"])
|
|
1191
|
+
|
|
1192
|
+
result = view.to_columns()
|
|
1193
|
+
assert result["a"] == approx([np.std(data["a"]), np.std(data["a"])])
|
|
1194
|
+
|
|
1195
|
+
# sort
|
|
1196
|
+
|
|
1197
|
+
def test_view_sort_int(self):
|
|
1198
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1199
|
+
tbl = Table(data)
|
|
1200
|
+
view = tbl.view(sort=[["a", "desc"]])
|
|
1201
|
+
assert view.to_records() == [{"a": 3, "b": 4}, {"a": 1, "b": 2}]
|
|
1202
|
+
|
|
1203
|
+
def test_view_sort_float(self):
|
|
1204
|
+
data = [{"a": 1.1, "b": 2}, {"a": 1.2, "b": 4}]
|
|
1205
|
+
tbl = Table(data)
|
|
1206
|
+
view = tbl.view(sort=[["a", "desc"]])
|
|
1207
|
+
assert view.to_records() == [{"a": 1.2, "b": 4}, {"a": 1.1, "b": 2}]
|
|
1208
|
+
|
|
1209
|
+
def test_view_sort_string(self):
|
|
1210
|
+
data = [{"a": "abc", "b": 2}, {"a": "def", "b": 4}]
|
|
1211
|
+
tbl = Table(data)
|
|
1212
|
+
view = tbl.view(sort=[["a", "desc"]])
|
|
1213
|
+
assert view.to_records() == [{"a": "def", "b": 4}, {"a": "abc", "b": 2}]
|
|
1214
|
+
|
|
1215
|
+
def test_view_sort_date(self, util):
|
|
1216
|
+
data = [{"a": date(2019, 7, 11), "b": 2}, {"a": date(2019, 7, 12), "b": 4}]
|
|
1217
|
+
tbl = Table(data)
|
|
1218
|
+
view = tbl.view(sort=[["a", "desc"]])
|
|
1219
|
+
assert view.to_records() == [
|
|
1220
|
+
{"a": util.to_timestamp(datetime(2019, 7, 12)), "b": 4},
|
|
1221
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11)), "b": 2},
|
|
1222
|
+
]
|
|
1223
|
+
|
|
1224
|
+
def test_view_sort_datetime(self, util):
|
|
1225
|
+
data = [
|
|
1226
|
+
{"a": datetime(2019, 7, 11, 8, 15), "b": 2},
|
|
1227
|
+
{"a": datetime(2019, 7, 11, 8, 16), "b": 4},
|
|
1228
|
+
]
|
|
1229
|
+
tbl = Table(data)
|
|
1230
|
+
view = tbl.view(sort=[["a", "desc"]])
|
|
1231
|
+
assert view.to_records() == [
|
|
1232
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11, 8, 16)), "b": 4},
|
|
1233
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11, 8, 15)), "b": 2},
|
|
1234
|
+
]
|
|
1235
|
+
|
|
1236
|
+
def test_view_sort_hidden(self):
|
|
1237
|
+
data = [{"a": 1.1, "b": 2}, {"a": 1.2, "b": 4}]
|
|
1238
|
+
tbl = Table(data)
|
|
1239
|
+
view = tbl.view(sort=[["a", "desc"]], columns=["b"])
|
|
1240
|
+
assert view.to_records() == [{"b": 4}, {"b": 2}]
|
|
1241
|
+
|
|
1242
|
+
def test_view_sort_avg_nan(self):
|
|
1243
|
+
data = {
|
|
1244
|
+
"w": [3.5, 4.5, None, None, None, None, 1.5, 2.5],
|
|
1245
|
+
"x": [1, 2, 3, 4, 4, 3, 2, 1],
|
|
1246
|
+
"y": ["a", "b", "c", "d", "e", "f", "g", "h"],
|
|
1247
|
+
}
|
|
1248
|
+
tbl = Table(data)
|
|
1249
|
+
view = tbl.view(
|
|
1250
|
+
columns=["x", "w"],
|
|
1251
|
+
group_by=["y"],
|
|
1252
|
+
sort=[["w", "asc"]],
|
|
1253
|
+
aggregates={"w": "avg", "x": "unique"},
|
|
1254
|
+
)
|
|
1255
|
+
assert view.to_columns() == {
|
|
1256
|
+
"__ROW_PATH__": [
|
|
1257
|
+
[],
|
|
1258
|
+
["c"],
|
|
1259
|
+
["d"],
|
|
1260
|
+
["e"],
|
|
1261
|
+
["f"],
|
|
1262
|
+
["g"],
|
|
1263
|
+
["h"],
|
|
1264
|
+
["a"],
|
|
1265
|
+
["b"],
|
|
1266
|
+
],
|
|
1267
|
+
"w": [3, None, None, None, None, 1.5, 2.5, 3.5, 4.5],
|
|
1268
|
+
"x": [None, 3, 4, 4, 3, 2, 1, 1, 2],
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
def test_view_sort_sum_nan(self):
|
|
1272
|
+
data = {
|
|
1273
|
+
"w": [3.5, 4.5, None, None, None, None, 1.5, 2.5],
|
|
1274
|
+
"x": [1, 2, 3, 4, 4, 3, 2, 1],
|
|
1275
|
+
"y": ["a", "b", "c", "d", "e", "f", "g", "h"],
|
|
1276
|
+
}
|
|
1277
|
+
tbl = Table(data)
|
|
1278
|
+
view = tbl.view(
|
|
1279
|
+
columns=["x", "w"],
|
|
1280
|
+
group_by=["y"],
|
|
1281
|
+
sort=[["w", "asc"]],
|
|
1282
|
+
aggregates={"w": "sum", "x": "unique"},
|
|
1283
|
+
)
|
|
1284
|
+
assert view.to_columns() == {
|
|
1285
|
+
"__ROW_PATH__": [
|
|
1286
|
+
[],
|
|
1287
|
+
["c"],
|
|
1288
|
+
["d"],
|
|
1289
|
+
["e"],
|
|
1290
|
+
["f"],
|
|
1291
|
+
["g"],
|
|
1292
|
+
["h"],
|
|
1293
|
+
["a"],
|
|
1294
|
+
["b"],
|
|
1295
|
+
],
|
|
1296
|
+
"w": [12, 0, 0, 0, 0, 1.5, 2.5, 3.5, 4.5],
|
|
1297
|
+
"x": [None, 3, 4, 4, 3, 2, 1, 1, 2],
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
def test_view_sort_unique_nan(self):
|
|
1301
|
+
data = {
|
|
1302
|
+
"w": [3.5, 4.5, None, None, None, None, 1.5, 2.5],
|
|
1303
|
+
"x": [1, 2, 3, 4, 4, 3, 2, 1],
|
|
1304
|
+
"y": ["a", "b", "c", "d", "e", "f", "g", "h"],
|
|
1305
|
+
}
|
|
1306
|
+
tbl = Table(data)
|
|
1307
|
+
view = tbl.view(
|
|
1308
|
+
columns=["x", "w"],
|
|
1309
|
+
group_by=["y"],
|
|
1310
|
+
sort=[["w", "asc"]],
|
|
1311
|
+
aggregates={"w": "unique", "x": "unique"},
|
|
1312
|
+
)
|
|
1313
|
+
assert view.to_columns() == {
|
|
1314
|
+
"__ROW_PATH__": [
|
|
1315
|
+
[],
|
|
1316
|
+
["c"],
|
|
1317
|
+
["d"],
|
|
1318
|
+
["e"],
|
|
1319
|
+
["f"],
|
|
1320
|
+
["g"],
|
|
1321
|
+
["h"],
|
|
1322
|
+
["a"],
|
|
1323
|
+
["b"],
|
|
1324
|
+
],
|
|
1325
|
+
"w": [None, None, None, None, None, 1.5, 2.5, 3.5, 4.5],
|
|
1326
|
+
"x": [None, 3, 4, 4, 3, 2, 1, 1, 2],
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
# filter
|
|
1330
|
+
|
|
1331
|
+
def test_view_filter_int_eq(self):
|
|
1332
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1333
|
+
tbl = Table(data)
|
|
1334
|
+
view = tbl.view(filter=[["a", "==", 1]])
|
|
1335
|
+
assert view.to_records() == [{"a": 1, "b": 2}]
|
|
1336
|
+
|
|
1337
|
+
def test_view_filter_int_neq(self):
|
|
1338
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1339
|
+
tbl = Table(data)
|
|
1340
|
+
view = tbl.view(filter=[["a", "!=", 1]])
|
|
1341
|
+
assert view.to_records() == [{"a": 3, "b": 4}]
|
|
1342
|
+
|
|
1343
|
+
def test_view_filter_int_gt(self):
|
|
1344
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1345
|
+
tbl = Table(data)
|
|
1346
|
+
view = tbl.view(filter=[["a", ">", 1]])
|
|
1347
|
+
assert view.to_records() == [{"a": 3, "b": 4}]
|
|
1348
|
+
|
|
1349
|
+
def test_view_filter_int_lt(self):
|
|
1350
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1351
|
+
tbl = Table(data)
|
|
1352
|
+
view = tbl.view(filter=[["a", "<", 3]])
|
|
1353
|
+
assert view.to_records() == [{"a": 1, "b": 2}]
|
|
1354
|
+
|
|
1355
|
+
def test_view_filter_float_eq(self):
|
|
1356
|
+
data = [{"a": 1.1, "b": 2}, {"a": 1.2, "b": 4}]
|
|
1357
|
+
tbl = Table(data)
|
|
1358
|
+
view = tbl.view(filter=[["a", "==", 1.2]])
|
|
1359
|
+
assert view.to_records() == [{"a": 1.2, "b": 4}]
|
|
1360
|
+
|
|
1361
|
+
def test_view_filter_float_neq(self):
|
|
1362
|
+
data = [{"a": 1.1, "b": 2}, {"a": 1.2, "b": 4}]
|
|
1363
|
+
tbl = Table(data)
|
|
1364
|
+
view = tbl.view(filter=[["a", "!=", 1.2]])
|
|
1365
|
+
assert view.to_records() == [{"a": 1.1, "b": 2}]
|
|
1366
|
+
|
|
1367
|
+
def test_view_filter_string_eq(self):
|
|
1368
|
+
data = [{"a": "abc", "b": 2}, {"a": "def", "b": 4}]
|
|
1369
|
+
tbl = Table(data)
|
|
1370
|
+
view = tbl.view(filter=[["a", "==", "def"]])
|
|
1371
|
+
assert view.to_records() == [{"a": "def", "b": 4}]
|
|
1372
|
+
|
|
1373
|
+
def test_view_filter_string_neq(self):
|
|
1374
|
+
data = [{"a": "abc", "b": 2}, {"a": "def", "b": 4}]
|
|
1375
|
+
tbl = Table(data)
|
|
1376
|
+
view = tbl.view(filter=[["a", "!=", "def"]])
|
|
1377
|
+
assert view.to_records() == [{"a": "abc", "b": 2}]
|
|
1378
|
+
|
|
1379
|
+
def test_view_filter_string_gt(self):
|
|
1380
|
+
data = [{"a": "abc", "b": 2}, {"a": "def", "b": 4}]
|
|
1381
|
+
tbl = Table(data)
|
|
1382
|
+
view = tbl.view(filter=[["a", ">", "abc"]])
|
|
1383
|
+
assert view.to_records() == [{"a": "def", "b": 4}]
|
|
1384
|
+
|
|
1385
|
+
def test_view_filter_string_lt(self):
|
|
1386
|
+
data = [{"a": "abc", "b": 2}, {"a": "def", "b": 4}]
|
|
1387
|
+
tbl = Table(data)
|
|
1388
|
+
view = tbl.view(filter=[["a", "<", "def"]])
|
|
1389
|
+
assert view.to_records() == [{"a": "abc", "b": 2}]
|
|
1390
|
+
|
|
1391
|
+
# @mark.skip # We do not support using `datetime.date` in operators.
|
|
1392
|
+
def test_view_filter_date_eq(self, util):
|
|
1393
|
+
data = [{"a": date(2019, 7, 11), "b": 2}, {"a": date(2019, 7, 12), "b": 4}]
|
|
1394
|
+
tbl = Table(data)
|
|
1395
|
+
view = tbl.view(filter=[["a", "==", str(date(2019, 7, 12))]])
|
|
1396
|
+
assert view.to_records() == [
|
|
1397
|
+
{"a": util.to_timestamp(datetime(2019, 7, 12)), "b": 4}
|
|
1398
|
+
]
|
|
1399
|
+
|
|
1400
|
+
# @mark.skip # We do not support using `datetime.date` in operators.
|
|
1401
|
+
def test_view_filter_date_neq(self, util):
|
|
1402
|
+
data = [{"a": date(2019, 7, 11), "b": 2}, {"a": date(2019, 7, 12), "b": 4}]
|
|
1403
|
+
tbl = Table(data)
|
|
1404
|
+
view = tbl.view(filter=[["a", "!=", str(date(2019, 7, 12))]])
|
|
1405
|
+
assert view.to_records() == [
|
|
1406
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11)), "b": 2}
|
|
1407
|
+
]
|
|
1408
|
+
|
|
1409
|
+
def test_view_filter_date_str_eq(self, util):
|
|
1410
|
+
data = [{"a": date(2019, 7, 11), "b": 2}, {"a": date(2019, 7, 12), "b": 4}]
|
|
1411
|
+
tbl = Table(data)
|
|
1412
|
+
view = tbl.view(filter=[["a", "==", "2019/7/12"]])
|
|
1413
|
+
assert view.to_records() == [
|
|
1414
|
+
{"a": util.to_timestamp(datetime(2019, 7, 12)), "b": 4}
|
|
1415
|
+
]
|
|
1416
|
+
|
|
1417
|
+
def test_view_filter_date_str_neq(self, util):
|
|
1418
|
+
data = [{"a": date(2019, 7, 11), "b": 2}, {"a": date(2019, 7, 12), "b": 4}]
|
|
1419
|
+
tbl = Table(data)
|
|
1420
|
+
view = tbl.view(filter=[["a", "!=", "2019/7/12"]])
|
|
1421
|
+
assert view.to_records() == [
|
|
1422
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11)), "b": 2}
|
|
1423
|
+
]
|
|
1424
|
+
|
|
1425
|
+
@mark.skip # We do not support using `datetime.datetime` in operators
|
|
1426
|
+
def test_view_filter_datetime_eq(self, util):
|
|
1427
|
+
data = [
|
|
1428
|
+
{"a": datetime(2019, 7, 11, 8, 15), "b": 2},
|
|
1429
|
+
{"a": datetime(2019, 7, 11, 8, 16), "b": 4},
|
|
1430
|
+
]
|
|
1431
|
+
tbl = Table(data)
|
|
1432
|
+
view = tbl.view(filter=[["a", "==", datetime(2019, 7, 11, 8, 15)]])
|
|
1433
|
+
assert view.to_records() == [
|
|
1434
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11, () * 1000)), "b": 2}
|
|
1435
|
+
]
|
|
1436
|
+
|
|
1437
|
+
@mark.skip # We do not support using `datetime.datetime` in operators
|
|
1438
|
+
def test_view_filter_datetime_neq(self, util):
|
|
1439
|
+
data = [
|
|
1440
|
+
{"a": datetime(2019, 7, 11, 8, 15), "b": 2},
|
|
1441
|
+
{"a": datetime(2019, 7, 11, 8, 16), "b": 4},
|
|
1442
|
+
]
|
|
1443
|
+
tbl = Table(data)
|
|
1444
|
+
view = tbl.view(filter=[["a", "!=", datetime(2019, 7, 11, 8, 15)]])
|
|
1445
|
+
assert view.to_records() == [
|
|
1446
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11, () * 1000)), "b": 4}
|
|
1447
|
+
]
|
|
1448
|
+
|
|
1449
|
+
@mark.skip # We do not support numpy anymore
|
|
1450
|
+
def test_view_filter_datetime_np_eq(self, util):
|
|
1451
|
+
data = [
|
|
1452
|
+
{"a": datetime(2019, 7, 11, 8, 15), "b": 2},
|
|
1453
|
+
{"a": datetime(2019, 7, 11, 8, 16), "b": 4},
|
|
1454
|
+
]
|
|
1455
|
+
tbl = Table(data)
|
|
1456
|
+
view = tbl.view(
|
|
1457
|
+
filter=[["a", "==", np.datetime64(datetime(2019, 7, 11, 8, 15))]]
|
|
1458
|
+
)
|
|
1459
|
+
assert view.to_records() == [
|
|
1460
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11, () * 1000)), "b": 2}
|
|
1461
|
+
]
|
|
1462
|
+
|
|
1463
|
+
@mark.skip # We do not support numpy anymore.
|
|
1464
|
+
def test_view_filter_datetime_np_neq(self, util):
|
|
1465
|
+
data = [
|
|
1466
|
+
{"a": datetime(2019, 7, 11, 8, 15), "b": 2},
|
|
1467
|
+
{"a": datetime(2019, 7, 11, 8, 16), "b": 4},
|
|
1468
|
+
]
|
|
1469
|
+
tbl = Table(data)
|
|
1470
|
+
view = tbl.view(
|
|
1471
|
+
filter=[["a", "!=", np.datetime64(datetime(2019, 7, 11, 8, 15))]]
|
|
1472
|
+
)
|
|
1473
|
+
assert view.to_records() == [
|
|
1474
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11, () * 1000)), "b": 4}
|
|
1475
|
+
]
|
|
1476
|
+
|
|
1477
|
+
def test_view_filter_datetime_as_number_eq(self, util):
|
|
1478
|
+
data = [
|
|
1479
|
+
{"a": datetime(2019, 7, 11, 8, 15).timestamp(), "b": 2},
|
|
1480
|
+
{"a": datetime(2019, 7, 11, 8, 16).timestamp(), "b": 4},
|
|
1481
|
+
]
|
|
1482
|
+
|
|
1483
|
+
tbl = Table({"a": "datetime", "b": "integer"})
|
|
1484
|
+
tbl.update(data)
|
|
1485
|
+
view = tbl.view(filter=[["a", "==", datetime(2019, 7, 11, 8, 15).timestamp()]])
|
|
1486
|
+
assert view.to_records() == [
|
|
1487
|
+
{"a": datetime(2019, 7, 11, 8, 15).timestamp(), "b": 2}
|
|
1488
|
+
]
|
|
1489
|
+
|
|
1490
|
+
def test_view_filter_datetime_as_number_neq(self, util):
|
|
1491
|
+
data = [
|
|
1492
|
+
{"a": datetime(2019, 7, 11, 8, 15).timestamp(), "b": 2},
|
|
1493
|
+
{"a": datetime(2019, 7, 11, 8, 16).timestamp(), "b": 4},
|
|
1494
|
+
]
|
|
1495
|
+
tbl = Table({"a": "datetime", "b": "integer"})
|
|
1496
|
+
tbl.update(data)
|
|
1497
|
+
view = tbl.view(filter=[["a", "!=", datetime(2019, 7, 11, 8, 15).timestamp()]])
|
|
1498
|
+
assert view.to_records() == [
|
|
1499
|
+
{"a": datetime(2019, 7, 11, 8, 16).timestamp(), "b": 4}
|
|
1500
|
+
]
|
|
1501
|
+
|
|
1502
|
+
def test_view_filter_datetime_str_eq(self, util):
|
|
1503
|
+
data = [
|
|
1504
|
+
{"a": datetime(2019, 7, 11, 8, 15), "b": 2},
|
|
1505
|
+
{"a": datetime(2019, 7, 11, 8, 16), "b": 4},
|
|
1506
|
+
]
|
|
1507
|
+
tbl = Table(data)
|
|
1508
|
+
view = tbl.view(filter=[["a", "==", "2019/7/11 8:15"]])
|
|
1509
|
+
assert view.to_records() == [
|
|
1510
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11, 8, 15)), "b": 2}
|
|
1511
|
+
]
|
|
1512
|
+
|
|
1513
|
+
def test_view_filter_datetime_str_neq(self, util):
|
|
1514
|
+
data = [
|
|
1515
|
+
{"a": datetime(2019, 7, 11, 8, 15), "b": 2},
|
|
1516
|
+
{"a": datetime(2019, 7, 11, 8, 16), "b": 4},
|
|
1517
|
+
]
|
|
1518
|
+
tbl = Table(data)
|
|
1519
|
+
view = tbl.view(filter=[["a", "!=", "2019/7/11 8:15"]])
|
|
1520
|
+
assert view.to_records() == [
|
|
1521
|
+
{"a": util.to_timestamp(datetime(2019, 7, 11, 8, 16)), "b": 4}
|
|
1522
|
+
]
|
|
1523
|
+
|
|
1524
|
+
def test_view_filter_string_is_none(self):
|
|
1525
|
+
data = [{"a": None, "b": 2}, {"a": "abc", "b": 4}]
|
|
1526
|
+
tbl = Table(data)
|
|
1527
|
+
view = tbl.view(
|
|
1528
|
+
filter=[["a", "is null", ""]]
|
|
1529
|
+
) # XXX: Having to add this "" is not soooo great.
|
|
1530
|
+
assert view.to_records() == [{"a": None, "b": 2}]
|
|
1531
|
+
|
|
1532
|
+
def test_view_filter_string_is_not_none(self):
|
|
1533
|
+
data = [{"a": None, "b": 2}, {"a": "abc", "b": 4}]
|
|
1534
|
+
tbl = Table(data)
|
|
1535
|
+
view = tbl.view(
|
|
1536
|
+
filter=[["a", "is not null", ""]]
|
|
1537
|
+
) # XXX: Having to add this "" is not soooo great.
|
|
1538
|
+
assert view.to_records() == [{"a": "abc", "b": 4}]
|
|
1539
|
+
|
|
1540
|
+
# on_update
|
|
1541
|
+
def test_view_on_update(self, sentinel):
|
|
1542
|
+
s = sentinel(False)
|
|
1543
|
+
|
|
1544
|
+
def callback(port_id):
|
|
1545
|
+
s.set(True)
|
|
1546
|
+
|
|
1547
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1548
|
+
tbl = Table(data)
|
|
1549
|
+
view = tbl.view()
|
|
1550
|
+
view.on_update(callback)
|
|
1551
|
+
tbl.update(data)
|
|
1552
|
+
assert s.get() is True
|
|
1553
|
+
|
|
1554
|
+
def test_view_on_update_multiple_callback(self, sentinel):
|
|
1555
|
+
s = sentinel(0)
|
|
1556
|
+
|
|
1557
|
+
def callback(port_id):
|
|
1558
|
+
s.set(s.get() + 1)
|
|
1559
|
+
|
|
1560
|
+
def callback1(port_id):
|
|
1561
|
+
s.set(s.get() - 1)
|
|
1562
|
+
|
|
1563
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1564
|
+
tbl = Table(data)
|
|
1565
|
+
view = tbl.view()
|
|
1566
|
+
view.on_update(callback)
|
|
1567
|
+
view.on_update(callback1)
|
|
1568
|
+
tbl.update(data)
|
|
1569
|
+
assert s.get() == 0
|
|
1570
|
+
|
|
1571
|
+
# on_delete
|
|
1572
|
+
|
|
1573
|
+
def test_view_on_delete(self, sentinel):
|
|
1574
|
+
s = sentinel(False)
|
|
1575
|
+
|
|
1576
|
+
def callback():
|
|
1577
|
+
s.set(True)
|
|
1578
|
+
|
|
1579
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1580
|
+
tbl = Table(data)
|
|
1581
|
+
view = tbl.view()
|
|
1582
|
+
view.on_delete(callback)
|
|
1583
|
+
view.delete()
|
|
1584
|
+
assert s.get() is True
|
|
1585
|
+
|
|
1586
|
+
# delete
|
|
1587
|
+
|
|
1588
|
+
def test_view_delete(self):
|
|
1589
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1590
|
+
tbl = Table(data)
|
|
1591
|
+
view = tbl.view()
|
|
1592
|
+
with raises(PerspectiveError):
|
|
1593
|
+
tbl.delete()
|
|
1594
|
+
view.delete()
|
|
1595
|
+
tbl.delete()
|
|
1596
|
+
|
|
1597
|
+
def test_view_delete_multiple_callbacks(self, sentinel):
|
|
1598
|
+
# make sure that callbacks on views get filtered
|
|
1599
|
+
s1 = sentinel(0)
|
|
1600
|
+
s2 = sentinel(0)
|
|
1601
|
+
|
|
1602
|
+
def cb1(port_id):
|
|
1603
|
+
s1.set(s1.get() + 1)
|
|
1604
|
+
|
|
1605
|
+
def cb2(port_id):
|
|
1606
|
+
s2.set(s2.get() + 1)
|
|
1607
|
+
|
|
1608
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1609
|
+
tbl = Table(data)
|
|
1610
|
+
v1 = tbl.view()
|
|
1611
|
+
v2 = tbl.view()
|
|
1612
|
+
v1.on_update(cb1)
|
|
1613
|
+
v2.on_update(cb2)
|
|
1614
|
+
tbl.update(data)
|
|
1615
|
+
assert s1.get() == 1
|
|
1616
|
+
assert s2.get() == 1
|
|
1617
|
+
v1.delete()
|
|
1618
|
+
tbl.update(data)
|
|
1619
|
+
assert s1.get() == 1
|
|
1620
|
+
assert s2.get() == 2
|
|
1621
|
+
|
|
1622
|
+
def test_view_delete_full_cleanup(self, sentinel):
|
|
1623
|
+
s = sentinel(0)
|
|
1624
|
+
|
|
1625
|
+
def cb1(port_id):
|
|
1626
|
+
s.set(s.get() + 1)
|
|
1627
|
+
|
|
1628
|
+
def cb2(port_id):
|
|
1629
|
+
s.set(s.get() + 2)
|
|
1630
|
+
|
|
1631
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1632
|
+
tbl = Table(data)
|
|
1633
|
+
v1 = tbl.view()
|
|
1634
|
+
v2 = tbl.view()
|
|
1635
|
+
v1.on_update(cb1)
|
|
1636
|
+
v2.on_update(cb2)
|
|
1637
|
+
v1.delete()
|
|
1638
|
+
v2.delete()
|
|
1639
|
+
tbl.update(data)
|
|
1640
|
+
assert s.get() == 0
|
|
1641
|
+
|
|
1642
|
+
# remove_update
|
|
1643
|
+
|
|
1644
|
+
def test_view_remove_update(self, sentinel):
|
|
1645
|
+
s = sentinel(0)
|
|
1646
|
+
|
|
1647
|
+
def cb1(port_id):
|
|
1648
|
+
s.set(s.get() + 1)
|
|
1649
|
+
|
|
1650
|
+
def cb2(port_id):
|
|
1651
|
+
s.set(s.get() + 2)
|
|
1652
|
+
|
|
1653
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1654
|
+
tbl = Table(data)
|
|
1655
|
+
view = tbl.view()
|
|
1656
|
+
t1 = view.on_update(cb1)
|
|
1657
|
+
view.on_update(cb2)
|
|
1658
|
+
view.remove_update(t1)
|
|
1659
|
+
tbl.update(data)
|
|
1660
|
+
assert s.get() == 2
|
|
1661
|
+
|
|
1662
|
+
def test_view_remove_multiple_update(self, sentinel):
|
|
1663
|
+
s1 = sentinel(0)
|
|
1664
|
+
s2 = sentinel(0)
|
|
1665
|
+
|
|
1666
|
+
def cb1(port_id):
|
|
1667
|
+
s1.set(s1.get() + 1)
|
|
1668
|
+
|
|
1669
|
+
def cb2(port_id):
|
|
1670
|
+
s2.set(s2.get() + 1)
|
|
1671
|
+
|
|
1672
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1673
|
+
tbl = Table(data)
|
|
1674
|
+
view = tbl.view()
|
|
1675
|
+
t1 = view.on_update(cb1)
|
|
1676
|
+
view.on_update(cb2)
|
|
1677
|
+
view.on_update(cb1)
|
|
1678
|
+
tbl.update(data)
|
|
1679
|
+
assert s1.get() == 2
|
|
1680
|
+
assert s2.get() == 1
|
|
1681
|
+
view.remove_update(t1)
|
|
1682
|
+
tbl.update(data)
|
|
1683
|
+
assert s1.get() == 3
|
|
1684
|
+
assert s2.get() == 2
|
|
1685
|
+
|
|
1686
|
+
# row delta
|
|
1687
|
+
|
|
1688
|
+
def test_view_row_delta_zero(self, util):
|
|
1689
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1690
|
+
update_data = {"a": [5], "b": [6]}
|
|
1691
|
+
|
|
1692
|
+
def cb1(port_id, delta):
|
|
1693
|
+
compare_delta(delta, update_data)
|
|
1694
|
+
|
|
1695
|
+
tbl = Table(data)
|
|
1696
|
+
view = tbl.view()
|
|
1697
|
+
view.on_update(cb1, mode="row")
|
|
1698
|
+
tbl.update(update_data)
|
|
1699
|
+
|
|
1700
|
+
def test_view_row_delta_zero_column_subset(self, util):
|
|
1701
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1702
|
+
update_data = {"a": [5], "b": [6]}
|
|
1703
|
+
|
|
1704
|
+
def cb1(port_id, delta):
|
|
1705
|
+
compare_delta(delta, {"b": [6]})
|
|
1706
|
+
|
|
1707
|
+
tbl = Table(data)
|
|
1708
|
+
view = tbl.view(columns=["b"])
|
|
1709
|
+
view.on_update(cb1, mode="row")
|
|
1710
|
+
tbl.update(update_data)
|
|
1711
|
+
|
|
1712
|
+
def test_view_row_delta_zero_from_schema(self, util):
|
|
1713
|
+
update_data = {"a": [5], "b": [6]}
|
|
1714
|
+
|
|
1715
|
+
def cb1(port_id, delta):
|
|
1716
|
+
compare_delta(delta, update_data)
|
|
1717
|
+
|
|
1718
|
+
tbl = Table({"a": "integer", "b": "integer"})
|
|
1719
|
+
view = tbl.view()
|
|
1720
|
+
view.on_update(cb1, mode="row")
|
|
1721
|
+
tbl.update(update_data)
|
|
1722
|
+
|
|
1723
|
+
def test_view_row_delta_zero_from_schema_column_subset(self, util):
|
|
1724
|
+
update_data = {"a": [5], "b": [6]}
|
|
1725
|
+
|
|
1726
|
+
def cb1(port_id, delta):
|
|
1727
|
+
compare_delta(delta, {"b": [6]})
|
|
1728
|
+
|
|
1729
|
+
tbl = Table({"a": "integer", "b": "integer"})
|
|
1730
|
+
|
|
1731
|
+
view = tbl.view(columns=["b"])
|
|
1732
|
+
view.on_update(cb1, mode="row")
|
|
1733
|
+
tbl.update(update_data)
|
|
1734
|
+
|
|
1735
|
+
def test_view_row_delta_zero_from_schema_filtered(self, util):
|
|
1736
|
+
update_data = {"a": [8, 9, 10, 11], "b": [1, 2, 3, 4]}
|
|
1737
|
+
|
|
1738
|
+
def cb1(port_id, delta):
|
|
1739
|
+
compare_delta(delta, {"a": [11], "b": [4]})
|
|
1740
|
+
|
|
1741
|
+
tbl = Table({"a": "integer", "b": "integer"})
|
|
1742
|
+
view = tbl.view(filter=[["a", ">", 10]])
|
|
1743
|
+
view.on_update(cb1, mode="row")
|
|
1744
|
+
tbl.update(update_data)
|
|
1745
|
+
|
|
1746
|
+
def test_view_row_delta_zero_from_schema_indexed(self, util):
|
|
1747
|
+
update_data = {"a": ["a", "b", "a"], "b": [1, 2, 3]}
|
|
1748
|
+
|
|
1749
|
+
def cb1(port_id, delta):
|
|
1750
|
+
compare_delta(delta, {"a": ["a", "b"], "b": [3, 2]})
|
|
1751
|
+
|
|
1752
|
+
tbl = Table({"a": "string", "b": "integer"}, index="a")
|
|
1753
|
+
|
|
1754
|
+
view = tbl.view()
|
|
1755
|
+
view.on_update(cb1, mode="row")
|
|
1756
|
+
|
|
1757
|
+
tbl.update(update_data)
|
|
1758
|
+
|
|
1759
|
+
def test_view_row_delta_zero_from_schema_indexed_filtered(self, util):
|
|
1760
|
+
update_data = {"a": [8, 9, 10, 11, 11], "b": [1, 2, 3, 4, 5]}
|
|
1761
|
+
|
|
1762
|
+
def cb1(port_id, delta):
|
|
1763
|
+
compare_delta(delta, {"a": [11], "b": [5]})
|
|
1764
|
+
|
|
1765
|
+
tbl = Table({"a": "integer", "b": "integer"}, index="a")
|
|
1766
|
+
view = tbl.view(filter=[["a", ">", 10]])
|
|
1767
|
+
view.on_update(cb1, mode="row")
|
|
1768
|
+
tbl.update(update_data)
|
|
1769
|
+
|
|
1770
|
+
def test_view_row_delta_one(self, util):
|
|
1771
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1772
|
+
update_data = {"a": [5], "b": [6]}
|
|
1773
|
+
|
|
1774
|
+
def cb1(port_id, delta):
|
|
1775
|
+
compare_delta(delta, {"a": [9, 5], "b": [12, 6]})
|
|
1776
|
+
|
|
1777
|
+
tbl = Table(data)
|
|
1778
|
+
view = tbl.view(group_by=["a"])
|
|
1779
|
+
assert view.to_columns() == {
|
|
1780
|
+
"__ROW_PATH__": [[], [1], [3]],
|
|
1781
|
+
"a": [4, 1, 3],
|
|
1782
|
+
"b": [6, 2, 4],
|
|
1783
|
+
}
|
|
1784
|
+
view.on_update(cb1, mode="row")
|
|
1785
|
+
tbl.update(update_data)
|
|
1786
|
+
|
|
1787
|
+
def test_view_row_delta_one_from_schema(self, util):
|
|
1788
|
+
update_data = {"a": [1, 2, 3, 4, 5], "b": [6, 7, 8, 9, 10]}
|
|
1789
|
+
|
|
1790
|
+
def cb1(port_id, delta):
|
|
1791
|
+
compare_delta(delta, {"a": [15, 1, 2, 3, 4, 5], "b": [40, 6, 7, 8, 9, 10]})
|
|
1792
|
+
|
|
1793
|
+
tbl = Table({"a": "integer", "b": "integer"})
|
|
1794
|
+
view = tbl.view(group_by=["a"])
|
|
1795
|
+
view.on_update(cb1, mode="row")
|
|
1796
|
+
tbl.update(update_data)
|
|
1797
|
+
|
|
1798
|
+
def test_view_row_delta_one_from_schema_sorted(self, util):
|
|
1799
|
+
update_data = {"a": [1, 2, 3, 4, 5], "b": [6, 7, 8, 9, 10]}
|
|
1800
|
+
|
|
1801
|
+
def cb1(port_id, delta):
|
|
1802
|
+
compare_delta(delta, {"a": [15, 5, 4, 3, 2, 1], "b": [40, 10, 9, 8, 7, 6]})
|
|
1803
|
+
|
|
1804
|
+
tbl = Table({"a": "integer", "b": "integer"})
|
|
1805
|
+
view = tbl.view(group_by=["a"], sort=[["a", "desc"]])
|
|
1806
|
+
view.on_update(cb1, mode="row")
|
|
1807
|
+
tbl.update(update_data)
|
|
1808
|
+
|
|
1809
|
+
def test_view_row_delta_one_from_schema_filtered(self, util):
|
|
1810
|
+
update_data = {"a": [1, 2, 3, 4, 5], "b": [6, 7, 8, 9, 10]}
|
|
1811
|
+
|
|
1812
|
+
def cb1(port_id, delta):
|
|
1813
|
+
compare_delta(delta, {"a": [9, 4, 5], "b": [19, 9, 10]})
|
|
1814
|
+
|
|
1815
|
+
tbl = Table({"a": "integer", "b": "integer"})
|
|
1816
|
+
view = tbl.view(group_by=["a"], filter=[["a", ">", 3]])
|
|
1817
|
+
view.on_update(cb1, mode="row")
|
|
1818
|
+
tbl.update(update_data)
|
|
1819
|
+
|
|
1820
|
+
def test_view_row_delta_one_from_schema_sorted_filtered(self, util):
|
|
1821
|
+
update_data = {"a": [1, 2, 3, 4, 5], "b": [6, 7, 8, 9, 10]}
|
|
1822
|
+
|
|
1823
|
+
def cb1(port_id, delta):
|
|
1824
|
+
compare_delta(delta, {"a": [9, 5, 4], "b": [19, 10, 9]})
|
|
1825
|
+
|
|
1826
|
+
tbl = Table({"a": "integer", "b": "integer"})
|
|
1827
|
+
view = tbl.view(group_by=["a"], sort=[["a", "desc"]], filter=[["a", ">", 3]])
|
|
1828
|
+
view.on_update(cb1, mode="row")
|
|
1829
|
+
tbl.update(update_data)
|
|
1830
|
+
|
|
1831
|
+
def test_view_row_delta_one_from_schema_indexed(self, util):
|
|
1832
|
+
update_data = {"a": [1, 2, 3, 4, 5, 5, 4], "b": [6, 7, 8, 9, 10, 11, 12]}
|
|
1833
|
+
|
|
1834
|
+
def cb1(port_id, delta):
|
|
1835
|
+
compare_delta(delta, {"a": [15, 1, 2, 3, 4, 5], "b": [44, 6, 7, 8, 12, 11]})
|
|
1836
|
+
|
|
1837
|
+
tbl = Table({"a": "integer", "b": "integer"}, index="a")
|
|
1838
|
+
|
|
1839
|
+
view = tbl.view(group_by=["a"])
|
|
1840
|
+
view.on_update(cb1, mode="row")
|
|
1841
|
+
|
|
1842
|
+
tbl.update(update_data)
|
|
1843
|
+
|
|
1844
|
+
def test_view_row_delta_one_from_schema_sorted_indexed(self, util):
|
|
1845
|
+
update_data = {"a": [1, 2, 3, 4, 5, 5, 4], "b": [6, 7, 8, 9, 10, 11, 12]}
|
|
1846
|
+
|
|
1847
|
+
def cb1(port_id, delta):
|
|
1848
|
+
compare_delta(delta, {"a": [15, 4, 5, 3, 2, 1], "b": [44, 12, 11, 8, 7, 6]})
|
|
1849
|
+
|
|
1850
|
+
tbl = Table({"a": "integer", "b": "integer"}, index="a")
|
|
1851
|
+
|
|
1852
|
+
view = tbl.view(group_by=["a"], sort=[["b", "desc"]])
|
|
1853
|
+
view.on_update(cb1, mode="row")
|
|
1854
|
+
|
|
1855
|
+
tbl.update(update_data)
|
|
1856
|
+
|
|
1857
|
+
def test_view_row_delta_one_from_schema_filtered_indexed(self, util):
|
|
1858
|
+
update_data = {"a": [1, 2, 3, 4, 5, 5, 4], "b": [6, 7, 8, 9, 10, 11, 12]}
|
|
1859
|
+
|
|
1860
|
+
def cb1(port_id, delta):
|
|
1861
|
+
compare_delta(delta, {"a": [9, 4, 5], "b": [23, 12, 11]})
|
|
1862
|
+
|
|
1863
|
+
tbl = Table({"a": "integer", "b": "integer"}, index="a")
|
|
1864
|
+
|
|
1865
|
+
view = tbl.view(group_by=["a"], filter=[["a", ">", 3]])
|
|
1866
|
+
view.on_update(cb1, mode="row")
|
|
1867
|
+
|
|
1868
|
+
tbl.update(update_data)
|
|
1869
|
+
|
|
1870
|
+
def test_view_row_delta_two(self, util):
|
|
1871
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1872
|
+
update_data = {"a": [5], "b": [6]}
|
|
1873
|
+
|
|
1874
|
+
def cb1(port_id, delta):
|
|
1875
|
+
compare_delta(
|
|
1876
|
+
delta,
|
|
1877
|
+
{
|
|
1878
|
+
"2|a": [1, None],
|
|
1879
|
+
"2|b": [2, None],
|
|
1880
|
+
"4|a": [3, None],
|
|
1881
|
+
"4|b": [4, None],
|
|
1882
|
+
"6|a": [5, 5],
|
|
1883
|
+
"6|b": [6, 6],
|
|
1884
|
+
},
|
|
1885
|
+
)
|
|
1886
|
+
|
|
1887
|
+
tbl = Table(data)
|
|
1888
|
+
view = tbl.view(group_by=["a"], split_by=["b"])
|
|
1889
|
+
assert view.to_columns() == {
|
|
1890
|
+
"__ROW_PATH__": [[], [1], [3]],
|
|
1891
|
+
"2|a": [1, 1, None],
|
|
1892
|
+
"2|b": [2, 2, None],
|
|
1893
|
+
"4|a": [3, None, 3],
|
|
1894
|
+
"4|b": [4, None, 4],
|
|
1895
|
+
}
|
|
1896
|
+
view.on_update(cb1, mode="row")
|
|
1897
|
+
tbl.update(update_data)
|
|
1898
|
+
|
|
1899
|
+
def test_view_row_delta_two_from_schema(self, util):
|
|
1900
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1901
|
+
|
|
1902
|
+
def cb1(port_id, delta):
|
|
1903
|
+
compare_delta(
|
|
1904
|
+
delta,
|
|
1905
|
+
{
|
|
1906
|
+
"2|a": [1, 1, None],
|
|
1907
|
+
"2|b": [2, 2, None],
|
|
1908
|
+
"4|a": [3, None, 3],
|
|
1909
|
+
"4|b": [4, None, 4],
|
|
1910
|
+
},
|
|
1911
|
+
)
|
|
1912
|
+
|
|
1913
|
+
tbl = Table({"a": "integer", "b": "integer"})
|
|
1914
|
+
view = tbl.view(group_by=["a"], split_by=["b"])
|
|
1915
|
+
view.on_update(cb1, mode="row")
|
|
1916
|
+
tbl.update(data)
|
|
1917
|
+
|
|
1918
|
+
def test_view_row_delta_two_from_schema_indexed(self, util):
|
|
1919
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 3, "b": 5}]
|
|
1920
|
+
|
|
1921
|
+
def cb1(port_id, delta):
|
|
1922
|
+
compare_delta(
|
|
1923
|
+
delta,
|
|
1924
|
+
{
|
|
1925
|
+
"2|a": [1, 1, None],
|
|
1926
|
+
"2|b": [2, 2, None],
|
|
1927
|
+
"5|a": [3, None, 3],
|
|
1928
|
+
"5|b": [5, None, 5],
|
|
1929
|
+
},
|
|
1930
|
+
)
|
|
1931
|
+
|
|
1932
|
+
tbl = Table({"a": "integer", "b": "integer"}, index="a")
|
|
1933
|
+
view = tbl.view(group_by=["a"], split_by=["b"])
|
|
1934
|
+
view.on_update(cb1, mode="row")
|
|
1935
|
+
tbl.update(data)
|
|
1936
|
+
|
|
1937
|
+
def test_view_row_delta_two_column_only(self, util):
|
|
1938
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1939
|
+
update_data = {"a": [5], "b": [6]}
|
|
1940
|
+
|
|
1941
|
+
def cb1(port_id, delta):
|
|
1942
|
+
compare_delta(
|
|
1943
|
+
delta,
|
|
1944
|
+
{
|
|
1945
|
+
"2|a": [1, None],
|
|
1946
|
+
"2|b": [2, None],
|
|
1947
|
+
"4|a": [3, None],
|
|
1948
|
+
"4|b": [4, None],
|
|
1949
|
+
"6|a": [5, 5],
|
|
1950
|
+
"6|b": [6, 6],
|
|
1951
|
+
},
|
|
1952
|
+
)
|
|
1953
|
+
|
|
1954
|
+
tbl = Table(data)
|
|
1955
|
+
view = tbl.view(split_by=["b"])
|
|
1956
|
+
assert view.to_columns() == {
|
|
1957
|
+
"2|a": [1, None],
|
|
1958
|
+
"2|b": [2, None],
|
|
1959
|
+
"4|a": [None, 3],
|
|
1960
|
+
"4|b": [None, 4],
|
|
1961
|
+
}
|
|
1962
|
+
view.on_update(cb1, mode="row")
|
|
1963
|
+
tbl.update(update_data)
|
|
1964
|
+
|
|
1965
|
+
def test_view_row_delta_two_column_only_indexed(self, util):
|
|
1966
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 3, "b": 5}]
|
|
1967
|
+
update_data = {"a": [5], "b": [6]}
|
|
1968
|
+
|
|
1969
|
+
def cb1(port_id, delta):
|
|
1970
|
+
compare_delta(
|
|
1971
|
+
delta,
|
|
1972
|
+
{
|
|
1973
|
+
"2|a": [1, None],
|
|
1974
|
+
"2|b": [2, None],
|
|
1975
|
+
"5|a": [3, None],
|
|
1976
|
+
"5|b": [5, None],
|
|
1977
|
+
"6|a": [5, 5],
|
|
1978
|
+
"6|b": [6, 6],
|
|
1979
|
+
},
|
|
1980
|
+
)
|
|
1981
|
+
|
|
1982
|
+
tbl = Table(data, index="a")
|
|
1983
|
+
view = tbl.view(split_by=["b"])
|
|
1984
|
+
assert view.to_columns() == {
|
|
1985
|
+
"2|a": [1, None],
|
|
1986
|
+
"2|b": [2, None],
|
|
1987
|
+
"5|a": [None, 3],
|
|
1988
|
+
"5|b": [None, 5],
|
|
1989
|
+
}
|
|
1990
|
+
view.on_update(cb1, mode="row")
|
|
1991
|
+
tbl.update(update_data)
|
|
1992
|
+
|
|
1993
|
+
def test_view_row_delta_two_column_only_from_schema(self, util):
|
|
1994
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
1995
|
+
|
|
1996
|
+
def cb1(port_id, delta):
|
|
1997
|
+
compare_delta(
|
|
1998
|
+
delta,
|
|
1999
|
+
{
|
|
2000
|
+
"2|a": [1, 1, None],
|
|
2001
|
+
"2|b": [2, 2, None],
|
|
2002
|
+
"4|a": [3, None, 3],
|
|
2003
|
+
"4|b": [4, None, 4],
|
|
2004
|
+
},
|
|
2005
|
+
)
|
|
2006
|
+
|
|
2007
|
+
tbl = Table({"a": "integer", "b": "integer"})
|
|
2008
|
+
view = tbl.view(split_by=["b"])
|
|
2009
|
+
view.on_update(cb1, mode="row")
|
|
2010
|
+
tbl.update(data)
|
|
2011
|
+
|
|
2012
|
+
def test_view_row_delta_two_column_only_from_schema_indexed(self, util):
|
|
2013
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}, {"a": 3, "b": 5}]
|
|
2014
|
+
|
|
2015
|
+
def cb1(port_id, delta):
|
|
2016
|
+
compare_delta(
|
|
2017
|
+
delta,
|
|
2018
|
+
{
|
|
2019
|
+
"2|a": [1, 1, None],
|
|
2020
|
+
"2|b": [2, 2, None],
|
|
2021
|
+
"5|a": [3, None, 3],
|
|
2022
|
+
"5|b": [5, None, 5],
|
|
2023
|
+
},
|
|
2024
|
+
)
|
|
2025
|
+
|
|
2026
|
+
tbl = Table({"a": "integer", "b": "integer"}, index="a")
|
|
2027
|
+
view = tbl.view(split_by=["b"])
|
|
2028
|
+
view.on_update(cb1, mode="row")
|
|
2029
|
+
tbl.update(data)
|
|
2030
|
+
|
|
2031
|
+
# hidden cols
|
|
2032
|
+
|
|
2033
|
+
def test_view_num_hidden_cols(self):
|
|
2034
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
2035
|
+
tbl = Table(data)
|
|
2036
|
+
view = tbl.view(columns=["a"], sort=[["b", "desc"]])
|
|
2037
|
+
cols = view.to_columns()
|
|
2038
|
+
assert cols == {"a": [3, 1]}
|
|
2039
|
+
|
|
2040
|
+
def test_view_context_two_update_clears_column_regression(self, util):
|
|
2041
|
+
"""Tests that, when a 2-sided View() is updated to a state where one of
|
|
2042
|
+
the column groups is empty, an infinite loop is not encountered.
|
|
2043
|
+
"""
|
|
2044
|
+
data = [
|
|
2045
|
+
{"a": "a", "b": 1, "c": 1.5, "i": 0},
|
|
2046
|
+
{"a": "a", "b": 2, "c": 2.5, "i": 1},
|
|
2047
|
+
{"a": "a", "b": 3, "c": 3.5, "i": 2},
|
|
2048
|
+
{"a": "b", "b": 1, "c": 4.5, "i": 3},
|
|
2049
|
+
{"a": "b", "b": 2, "c": 5.5, "i": 4},
|
|
2050
|
+
{"a": "b", "b": 3, "c": 6.5, "i": 5},
|
|
2051
|
+
]
|
|
2052
|
+
|
|
2053
|
+
tbl = Table(data, index="i")
|
|
2054
|
+
view = tbl.view(
|
|
2055
|
+
group_by=["b"],
|
|
2056
|
+
split_by=["a"],
|
|
2057
|
+
columns=["c"],
|
|
2058
|
+
filter=[["c", ">", 0]],
|
|
2059
|
+
sort=[["c", "asc"], ["a", "col asc"]],
|
|
2060
|
+
)
|
|
2061
|
+
|
|
2062
|
+
assert view.to_records() == [
|
|
2063
|
+
{"__ROW_PATH__": [], "a|c": 7.5, "b|c": 16.5},
|
|
2064
|
+
{"__ROW_PATH__": [1], "a|c": 1.5, "b|c": 4.5},
|
|
2065
|
+
{"__ROW_PATH__": [2], "a|c": 2.5, "b|c": 5.5},
|
|
2066
|
+
{"__ROW_PATH__": [3], "a|c": 3.5, "b|c": 6.5},
|
|
2067
|
+
]
|
|
2068
|
+
|
|
2069
|
+
tbl.update(
|
|
2070
|
+
[
|
|
2071
|
+
{"c": -1, "i": 0},
|
|
2072
|
+
{"c": -1, "i": 1},
|
|
2073
|
+
{"c": -1, "i": 2},
|
|
2074
|
+
]
|
|
2075
|
+
)
|
|
2076
|
+
|
|
2077
|
+
assert view.to_records() == [
|
|
2078
|
+
{"__ROW_PATH__": [], "b|c": 16.5},
|
|
2079
|
+
{"__ROW_PATH__": [1], "b|c": 4.5},
|
|
2080
|
+
{"__ROW_PATH__": [2], "b|c": 5.5},
|
|
2081
|
+
{"__ROW_PATH__": [3], "b|c": 6.5},
|
|
2082
|
+
]
|
|
2083
|
+
|
|
2084
|
+
tbl.update(
|
|
2085
|
+
[
|
|
2086
|
+
{"a": "a", "b": 1, "c": 1.5, "i": 6},
|
|
2087
|
+
{"a": "a", "b": 2, "c": 2.5, "i": 7},
|
|
2088
|
+
{"a": "a", "b": 3, "c": 3.5, "i": 8},
|
|
2089
|
+
]
|
|
2090
|
+
)
|
|
2091
|
+
|
|
2092
|
+
assert view.to_records() == [
|
|
2093
|
+
{"__ROW_PATH__": [], "a|c": 7.5, "b|c": 16.5},
|
|
2094
|
+
{"__ROW_PATH__": [1], "a|c": 1.5, "b|c": 4.5},
|
|
2095
|
+
{"__ROW_PATH__": [2], "a|c": 2.5, "b|c": 5.5},
|
|
2096
|
+
{"__ROW_PATH__": [3], "a|c": 3.5, "b|c": 6.5},
|
|
2097
|
+
]
|
|
2098
|
+
|
|
2099
|
+
assert tbl.size() == 9
|
|
2100
|
+
|
|
2101
|
+
# expand/collapse
|
|
2102
|
+
|
|
2103
|
+
def test_view_collapse_one(self):
|
|
2104
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
2105
|
+
tbl = Table(data)
|
|
2106
|
+
view = tbl.view(group_by=["a"])
|
|
2107
|
+
assert view.collapse(0) == 2
|
|
2108
|
+
|
|
2109
|
+
def test_view_collapse_two(self):
|
|
2110
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2111
|
+
tbl = Table(data)
|
|
2112
|
+
view = tbl.view(group_by=["a"], split_by=["c"])
|
|
2113
|
+
assert view.collapse(0) == 2
|
|
2114
|
+
|
|
2115
|
+
# TODO collapse/espand should be no-ops on column only contexts, but
|
|
2116
|
+
# the concept of "column only" is not yet implemented in C++
|
|
2117
|
+
@mark.skip
|
|
2118
|
+
def test_view_collapse_two_column_only(self):
|
|
2119
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2120
|
+
tbl = Table(data)
|
|
2121
|
+
view = tbl.view(split_by=["c"])
|
|
2122
|
+
assert view.collapse(0) == 0
|
|
2123
|
+
|
|
2124
|
+
def test_view_expand_one(self):
|
|
2125
|
+
data = [{"a": 1, "b": 2}, {"a": 3, "b": 4}]
|
|
2126
|
+
tbl = Table(data)
|
|
2127
|
+
view = tbl.view(group_by=["a"])
|
|
2128
|
+
assert view.expand(0) == 0
|
|
2129
|
+
|
|
2130
|
+
def test_view_expand_two(self):
|
|
2131
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2132
|
+
tbl = Table(data)
|
|
2133
|
+
view = tbl.view(group_by=["a"], split_by=["c"])
|
|
2134
|
+
assert view.expand(1) == 1
|
|
2135
|
+
|
|
2136
|
+
def test_view_expand_two_column_only(self):
|
|
2137
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2138
|
+
tbl = Table(data)
|
|
2139
|
+
view = tbl.view(split_by=["c"])
|
|
2140
|
+
assert view.expand(0) == 0
|
|
2141
|
+
|
|
2142
|
+
# view config validation
|
|
2143
|
+
|
|
2144
|
+
def test_invalid_column_should_throw(self):
|
|
2145
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2146
|
+
tbl = Table(data)
|
|
2147
|
+
with raises(PerspectiveError) as ex:
|
|
2148
|
+
tbl.view(columns=["x"])
|
|
2149
|
+
assert str(ex.value) == "Abort(): Invalid column 'x' found in View columns.\n"
|
|
2150
|
+
|
|
2151
|
+
def test_invalid_column_should_throw_and_updates_should_work(self):
|
|
2152
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2153
|
+
tbl = Table(data)
|
|
2154
|
+
|
|
2155
|
+
with raises(PerspectiveError) as ex:
|
|
2156
|
+
tbl.view(columns=["x"])
|
|
2157
|
+
assert str(ex.value) == "Abort(): Invalid column 'x' found in View columns.\n"
|
|
2158
|
+
|
|
2159
|
+
for i in range(100):
|
|
2160
|
+
tbl.update(data)
|
|
2161
|
+
# force call to _process which should shake out invalid column ptrs
|
|
2162
|
+
tbl.size()
|
|
2163
|
+
|
|
2164
|
+
view2 = tbl.view()
|
|
2165
|
+
assert view2.num_rows() == 202
|
|
2166
|
+
|
|
2167
|
+
def test_invalid_column_aggregate_should_throw(self):
|
|
2168
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2169
|
+
tbl = Table(data)
|
|
2170
|
+
|
|
2171
|
+
with raises(PerspectiveError) as ex:
|
|
2172
|
+
tbl.view(columns=["x"], aggregates={"x": "sum"})
|
|
2173
|
+
|
|
2174
|
+
assert str(ex.value) == "Abort(): Invalid column 'x' found in View columns.\n"
|
|
2175
|
+
|
|
2176
|
+
def test_invalid_column_aggregate_should_throw_and_updates_should_work(self):
|
|
2177
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2178
|
+
tbl = Table(data)
|
|
2179
|
+
|
|
2180
|
+
with raises(PerspectiveError) as ex:
|
|
2181
|
+
tbl.view(columns=["x"], aggregates={"x": "sum"})
|
|
2182
|
+
|
|
2183
|
+
assert str(ex.value) == "Abort(): Invalid column 'x' found in View columns.\n"
|
|
2184
|
+
|
|
2185
|
+
for i in range(100):
|
|
2186
|
+
tbl.update(data)
|
|
2187
|
+
# force call to _process which should shake out invalid column ptrs
|
|
2188
|
+
tbl.size()
|
|
2189
|
+
|
|
2190
|
+
view2 = tbl.view()
|
|
2191
|
+
assert view2.num_rows() == 202
|
|
2192
|
+
|
|
2193
|
+
def test_invalid_group_by_should_throw(self):
|
|
2194
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2195
|
+
tbl = Table(data)
|
|
2196
|
+
with raises(PerspectiveError) as ex:
|
|
2197
|
+
tbl.view(group_by=["x"])
|
|
2198
|
+
assert str(ex.value) == "Abort(): Invalid column 'x' found in View group_by.\n"
|
|
2199
|
+
|
|
2200
|
+
def test_invalid_split_by_should_throw(self):
|
|
2201
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2202
|
+
tbl = Table(data)
|
|
2203
|
+
with raises(PerspectiveError) as ex:
|
|
2204
|
+
tbl.view(split_by=["x"])
|
|
2205
|
+
assert str(ex.value) == "Abort(): Invalid column 'x' found in View split_by.\n"
|
|
2206
|
+
|
|
2207
|
+
def test_invalid_filters_should_throw(self):
|
|
2208
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2209
|
+
tbl = Table(data)
|
|
2210
|
+
with raises(PerspectiveError) as ex:
|
|
2211
|
+
tbl.view(filter=[["x", "==", "abc"]])
|
|
2212
|
+
assert str(ex.value) == "Abort(): Filter column not in schema: x"
|
|
2213
|
+
|
|
2214
|
+
def test_invalid_sorts_should_throw(self):
|
|
2215
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2216
|
+
tbl = Table(data)
|
|
2217
|
+
with raises(PerspectiveError) as ex:
|
|
2218
|
+
tbl.view(sort=[["x", "desc"]])
|
|
2219
|
+
assert str(ex.value) == "Abort(): Invalid column 'x' found in View sorts.\n"
|
|
2220
|
+
|
|
2221
|
+
def test_should_throw_on_first_invalid(self):
|
|
2222
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2223
|
+
tbl = Table(data)
|
|
2224
|
+
with raises(PerspectiveError) as ex:
|
|
2225
|
+
tbl.view(
|
|
2226
|
+
group_by=["a"],
|
|
2227
|
+
split_by=["c"],
|
|
2228
|
+
filter=[["a", ">", 1]],
|
|
2229
|
+
aggregates={"a": "avg"},
|
|
2230
|
+
sort=[["x", "desc"]],
|
|
2231
|
+
)
|
|
2232
|
+
assert str(ex.value) == "Abort(): Invalid column 'x' found in View sorts.\n"
|
|
2233
|
+
|
|
2234
|
+
def test_invalid_columns_not_in_expression_should_throw(self):
|
|
2235
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2236
|
+
tbl = Table(data)
|
|
2237
|
+
with raises(PerspectiveError) as ex:
|
|
2238
|
+
tbl.view(columns=["abc", "x"], expressions={"abc": "1 + 2"})
|
|
2239
|
+
assert str(ex.value) == "Abort(): Invalid column 'x' found in View columns.\n"
|
|
2240
|
+
|
|
2241
|
+
def test_should_not_throw_valid_expression(self):
|
|
2242
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2243
|
+
tbl = Table(data)
|
|
2244
|
+
view = tbl.view(columns=["abc"], expressions={"abc": "'hello!'"})
|
|
2245
|
+
|
|
2246
|
+
assert view.schema() == {"abc": "string"}
|
|
2247
|
+
|
|
2248
|
+
def test_should_not_throw_valid_expression_config(self):
|
|
2249
|
+
data = [{"a": 1, "b": 2, "c": "a"}, {"a": 3, "b": 4, "c": "b"}]
|
|
2250
|
+
tbl = Table(data)
|
|
2251
|
+
view = tbl.view(
|
|
2252
|
+
aggregates={"abc": "dominant"},
|
|
2253
|
+
columns=["abc"],
|
|
2254
|
+
sort=[["abc", "desc"]],
|
|
2255
|
+
filter=[["abc", "==", "A"]],
|
|
2256
|
+
group_by=["abc"],
|
|
2257
|
+
split_by=["abc"],
|
|
2258
|
+
expressions={"abc": "'hello!'"},
|
|
2259
|
+
)
|
|
2260
|
+
|
|
2261
|
+
assert view.schema() == {"abc": "string"}
|