dataiter 0.49__tar.gz → 0.51__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dataiter-0.49 → dataiter-0.51}/COPYING +1 -1
- {dataiter-0.49 → dataiter-0.51}/PKG-INFO +3 -2
- {dataiter-0.49 → dataiter-0.51}/dataiter/__init__.py +1 -1
- {dataiter-0.49 → dataiter-0.51}/dataiter/data_frame.py +2 -2
- {dataiter-0.49 → dataiter-0.51}/dataiter/list_of_dicts.py +55 -4
- {dataiter-0.49 → dataiter-0.51}/dataiter/test/test_list_of_dicts.py +16 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/test/test_util.py +9 -4
- {dataiter-0.49 → dataiter-0.51}/dataiter/util.py +23 -16
- {dataiter-0.49 → dataiter-0.51}/dataiter/vector.py +3 -3
- {dataiter-0.49 → dataiter-0.51}/pyproject.toml +2 -2
- {dataiter-0.49 → dataiter-0.51}/.gitignore +0 -0
- {dataiter-0.49 → dataiter-0.51}/AUTHORS.md +0 -0
- {dataiter-0.49 → dataiter-0.51}/README.md +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/aggregate.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/deco.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/dt.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/geojson.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/io.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/test/__init__.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/test/test_aggregate.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/test/test_data_frame.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/test/test_dt.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/test/test_geojson.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/test/test_io.py +0 -0
- {dataiter-0.49 → dataiter-0.51}/dataiter/test/test_vector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dataiter
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.51
|
|
4
4
|
Summary: Classes for data manipulation
|
|
5
5
|
Project-URL: Homepage, https://github.com/otsaloma/dataiter
|
|
6
6
|
Author-email: Osmo Salomaa <otsaloma@iki.fi>
|
|
@@ -9,8 +9,9 @@ License-File: AUTHORS.md
|
|
|
9
9
|
License-File: COPYING
|
|
10
10
|
Requires-Python: >=3.6.0
|
|
11
11
|
Requires-Dist: attd>=0.3
|
|
12
|
-
Requires-Dist: numpy
|
|
12
|
+
Requires-Dist: numpy<2.0,>=1.7
|
|
13
13
|
Requires-Dist: pandas>=1.0
|
|
14
|
+
Requires-Dist: wcwidth>=0.1
|
|
14
15
|
Description-Content-Type: text/markdown
|
|
15
16
|
|
|
16
17
|
Python Classes for Data Manipulation
|
|
@@ -1185,7 +1185,7 @@ class DataFrame(dict):
|
|
|
1185
1185
|
max_width = max_width or util.get_print_width()
|
|
1186
1186
|
truncate_width = truncate_width or dataiter.PRINT_TRUNCATE_WIDTH
|
|
1187
1187
|
n = min(self.nrow, max_rows)
|
|
1188
|
-
columns = {colname: util.
|
|
1188
|
+
columns = {colname: util.upad(
|
|
1189
1189
|
[colname] +
|
|
1190
1190
|
[str(column.dtype)] +
|
|
1191
1191
|
[str(x) for x in column[:n].to_strings(
|
|
@@ -1194,7 +1194,7 @@ class DataFrame(dict):
|
|
|
1194
1194
|
for column in columns.values():
|
|
1195
1195
|
column.insert(2, "─" * util.ulen(column[0]))
|
|
1196
1196
|
row_numbers = [str(i) for i in range(n)]
|
|
1197
|
-
row_numbers = util.
|
|
1197
|
+
row_numbers = util.upad(["", "", ""] + row_numbers)
|
|
1198
1198
|
# If the length of rows exceeds max_width, split to
|
|
1199
1199
|
# batches of columns (like R's print.data.frame).
|
|
1200
1200
|
rows_to_print = []
|
|
@@ -28,6 +28,7 @@ import json
|
|
|
28
28
|
import operator
|
|
29
29
|
import pickle
|
|
30
30
|
import random
|
|
31
|
+
import sys
|
|
31
32
|
|
|
32
33
|
from attd import AttributeDict
|
|
33
34
|
from dataiter import deco
|
|
@@ -219,6 +220,18 @@ class ListOfDicts(list):
|
|
|
219
220
|
"""
|
|
220
221
|
return self.__deepcopy__()
|
|
221
222
|
|
|
223
|
+
@deco.new_from_generator
|
|
224
|
+
def drop_na(self, *keys):
|
|
225
|
+
"""
|
|
226
|
+
Return list without items that have missing values in `keys`.
|
|
227
|
+
|
|
228
|
+
>>> data = di.read_json("data/listings.json")
|
|
229
|
+
>>> data.drop_na("sqft")
|
|
230
|
+
"""
|
|
231
|
+
for item in self:
|
|
232
|
+
if not any(item.get(x, None) is None for x in keys):
|
|
233
|
+
yield item
|
|
234
|
+
|
|
222
235
|
@deco.new_from_generator
|
|
223
236
|
def extend(self, other):
|
|
224
237
|
"""
|
|
@@ -245,8 +258,7 @@ class ListOfDicts(list):
|
|
|
245
258
|
>>> data = data.fill_missing_keys()
|
|
246
259
|
"""
|
|
247
260
|
if not key_value_pairs:
|
|
248
|
-
|
|
249
|
-
key_value_pairs = dict.fromkeys(keys, None)
|
|
261
|
+
key_value_pairs = dict.fromkeys(self.keys(), None)
|
|
250
262
|
key_value_pairs = key_value_pairs.items()
|
|
251
263
|
for item in self:
|
|
252
264
|
for key, value in key_value_pairs:
|
|
@@ -423,6 +435,15 @@ class ListOfDicts(list):
|
|
|
423
435
|
yield item
|
|
424
436
|
yield self[i]
|
|
425
437
|
|
|
438
|
+
def keys(self):
|
|
439
|
+
"""
|
|
440
|
+
Return an iterator over unique keys in all items.
|
|
441
|
+
|
|
442
|
+
>>> data = di.read_json("data/listings.json")
|
|
443
|
+
>>> list(data.keys())
|
|
444
|
+
"""
|
|
445
|
+
yield from dict.fromkeys(itertools.chain(*self))
|
|
446
|
+
|
|
426
447
|
@deco.obsoletes
|
|
427
448
|
@deco.new_from_generator
|
|
428
449
|
def left_join(self, other, *by):
|
|
@@ -539,6 +560,36 @@ class ListOfDicts(list):
|
|
|
539
560
|
"""
|
|
540
561
|
print(self.to_string(max_items=max_items))
|
|
541
562
|
|
|
563
|
+
def print_memory_use(self):
|
|
564
|
+
"""
|
|
565
|
+
Print memory use by key and total.
|
|
566
|
+
|
|
567
|
+
>>> data = di.read_json("data/listings.json")
|
|
568
|
+
>>> data.print_memory_use()
|
|
569
|
+
"""
|
|
570
|
+
from dataiter import DataFrame
|
|
571
|
+
mem = DataFrame()
|
|
572
|
+
for key in self.keys():
|
|
573
|
+
new = DataFrame(key=key)
|
|
574
|
+
values = self.pluck(key)
|
|
575
|
+
values_real = list(filter(None, values))
|
|
576
|
+
first = values_real[0] if values_real else None
|
|
577
|
+
total = sum(sys.getsizeof(x) for x in values)
|
|
578
|
+
new.type = first.__class__.__name__
|
|
579
|
+
new.item_size = int(round(total / len(values)))
|
|
580
|
+
new.total_size = total
|
|
581
|
+
mem = mem.rbind(new)
|
|
582
|
+
new = DataFrame(key="TOTAL")
|
|
583
|
+
new.type = "--"
|
|
584
|
+
new.item_size = mem.item_size.sum()
|
|
585
|
+
new.total_size = mem.total_size.sum()
|
|
586
|
+
mem = mem.rbind(new)
|
|
587
|
+
# Format sizes into sensible values for display.
|
|
588
|
+
mem.item_size = [f"{x:.0f} B" for x in mem.item_size]
|
|
589
|
+
mem.total_size = [f"{x/1024**2:,.0f} MB" for x in mem.total_size]
|
|
590
|
+
mem.colnames = [x.upper() for x in mem.colnames]
|
|
591
|
+
print(mem)
|
|
592
|
+
|
|
542
593
|
def print_na_counts(self):
|
|
543
594
|
"""
|
|
544
595
|
Print counts of missing values by key.
|
|
@@ -550,7 +601,7 @@ class ListOfDicts(list):
|
|
|
550
601
|
>>> data.print_na_counts()
|
|
551
602
|
"""
|
|
552
603
|
print("Missing counts:")
|
|
553
|
-
for key in
|
|
604
|
+
for key in self.keys():
|
|
554
605
|
n = sum(x.get(key, None) is None for x in self)
|
|
555
606
|
if n == 0: continue
|
|
556
607
|
pc = 100 * n / len(self)
|
|
@@ -838,7 +889,7 @@ class ListOfDicts(list):
|
|
|
838
889
|
if not self:
|
|
839
890
|
raise ValueError("Cannot write empty CSV file")
|
|
840
891
|
# Take a superset of all keys.
|
|
841
|
-
keys =
|
|
892
|
+
keys = list(self.keys())
|
|
842
893
|
util.makedirs_for_file(path)
|
|
843
894
|
with util.xopen(path, "wt", encoding=encoding) as f:
|
|
844
895
|
writer = csv.DictWriter(f,
|
|
@@ -171,6 +171,12 @@ class TestListOfDicts:
|
|
|
171
171
|
assert b._predecessor is a
|
|
172
172
|
assert c._predecessor is None
|
|
173
173
|
|
|
174
|
+
def test_drop_na(self):
|
|
175
|
+
data = test.list_of_dicts("listings.json")
|
|
176
|
+
data = data.drop_na("sqft")
|
|
177
|
+
assert len(data) == 396
|
|
178
|
+
assert None not in data.pluck("sqft")
|
|
179
|
+
|
|
174
180
|
def test_extend(self):
|
|
175
181
|
orig = test.list_of_dicts("downloads.json")
|
|
176
182
|
data = orig.extend(orig)
|
|
@@ -260,6 +266,10 @@ class TestListOfDicts:
|
|
|
260
266
|
assert isinstance(data[100], AttributeDict)
|
|
261
267
|
assert data[100] == item
|
|
262
268
|
|
|
269
|
+
def test_keys(self):
|
|
270
|
+
data = test.list_of_dicts("listings.json")
|
|
271
|
+
assert list(data.keys()) == ["id", "hood", "zipcode", "guests", "sqft", "price"]
|
|
272
|
+
|
|
263
273
|
def test_left_join(self):
|
|
264
274
|
orig = test.list_of_dicts("downloads.json")
|
|
265
275
|
holidays = test.list_of_dicts("holidays.json")
|
|
@@ -321,6 +331,12 @@ class TestListOfDicts:
|
|
|
321
331
|
assert len(downloads) == len(data)
|
|
322
332
|
assert sum(downloads) == 541300639
|
|
323
333
|
|
|
334
|
+
@patch("builtins.print")
|
|
335
|
+
def test_print_memory_use(self, mock_print):
|
|
336
|
+
data = test.list_of_dicts("vehicles.json")
|
|
337
|
+
data.print_memory_use()
|
|
338
|
+
mock_print.assert_called()
|
|
339
|
+
|
|
324
340
|
@patch("builtins.print")
|
|
325
341
|
def test_print_na_counts(self, mock_print):
|
|
326
342
|
data = test.list_of_dicts("vehicles.json")
|
|
@@ -112,10 +112,6 @@ class TestUtil:
|
|
|
112
112
|
assert util.length([1]) == 1
|
|
113
113
|
assert util.length([1, 2]) == 2
|
|
114
114
|
|
|
115
|
-
def test_pad(self):
|
|
116
|
-
assert util.pad(["a", "aa", "aaa"], align="right") == [" a", " aa", "aaa"]
|
|
117
|
-
assert util.pad(["a", "aa", "aaa"], align="left") == ["a ", "aa ", "aaa"]
|
|
118
|
-
|
|
119
115
|
def test_quote(self):
|
|
120
116
|
assert util.quote("hello") == '"hello"'
|
|
121
117
|
assert util.quote('"hello"') == '"\\"hello\\""'
|
|
@@ -140,6 +136,15 @@ class TestUtil:
|
|
|
140
136
|
def test_unique_types(self):
|
|
141
137
|
assert util.unique_types([1, 2, 3.3, np.nan, None]) == {int, float}
|
|
142
138
|
|
|
139
|
+
def test_upad(self):
|
|
140
|
+
assert util.upad(["a", "aa", "aaa"], align="right") == [" a", " aa", "aaa"]
|
|
141
|
+
assert util.upad(["a", "aa", "aaa"], align="left") == ["a ", "aa ", "aaa"]
|
|
142
|
+
|
|
143
|
+
def test_utruncate(self):
|
|
144
|
+
assert util.utruncate("abcdef", 4) == "abcd"
|
|
145
|
+
assert util.utruncate("abc\u200bdef", 4) == "abc\u200bd"
|
|
146
|
+
assert util.utruncate("abc\u200bdef\u200b", 4) == "abc\u200bd"
|
|
147
|
+
|
|
143
148
|
def test_xopen_bz2(self):
|
|
144
149
|
text = "test åäö"
|
|
145
150
|
handle, path = tempfile.mkstemp(".bz2")
|
|
@@ -31,6 +31,7 @@ import numpy as np
|
|
|
31
31
|
import os
|
|
32
32
|
import shutil
|
|
33
33
|
import string
|
|
34
|
+
import wcwidth
|
|
34
35
|
|
|
35
36
|
from dataiter import deco
|
|
36
37
|
from pathlib import Path
|
|
@@ -91,15 +92,6 @@ def length(value):
|
|
|
91
92
|
def makedirs_for_file(path):
|
|
92
93
|
return Path(path).parent.mkdir(parents=True, exist_ok=True)
|
|
93
94
|
|
|
94
|
-
@deco.listify
|
|
95
|
-
def pad(strings, *, align="right"):
|
|
96
|
-
width = max(ulen(x) for x in strings)
|
|
97
|
-
for value in strings:
|
|
98
|
-
padding = " " * (width - ulen(value))
|
|
99
|
-
yield (padding + value
|
|
100
|
-
if align == "right"
|
|
101
|
-
else value + padding)
|
|
102
|
-
|
|
103
95
|
def parse_env_boolean(name):
|
|
104
96
|
return {
|
|
105
97
|
"1": True,
|
|
@@ -132,19 +124,34 @@ def unique_keys(keys):
|
|
|
132
124
|
|
|
133
125
|
def ulen(string):
|
|
134
126
|
# Return the display length of string accounting for
|
|
135
|
-
# Unicode characters that have a display width
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
.replace("\u200c", "") # zero width non-joiner
|
|
139
|
-
.replace("\u200d", "") # zero width joiner
|
|
140
|
-
.replace("\u2060", "") # word joiner
|
|
141
|
-
)
|
|
127
|
+
# Unicode characters that have a display width != 1.
|
|
128
|
+
length = wcwidth.wcswidth(string)
|
|
129
|
+
return length if length >= 0 else 0
|
|
142
130
|
|
|
143
131
|
def unique_types(seq):
|
|
144
132
|
return set(x.__class__ for x in seq if
|
|
145
133
|
x is not None and
|
|
146
134
|
not (isinstance(x, float) and np.isnan(x)))
|
|
147
135
|
|
|
136
|
+
@deco.listify
|
|
137
|
+
def upad(strings, *, align="right"):
|
|
138
|
+
# Pad strings for display accounting for
|
|
139
|
+
# Unicode characters that have a display width != 1.
|
|
140
|
+
width = max(ulen(x) for x in strings)
|
|
141
|
+
for value in strings:
|
|
142
|
+
padding = " " * (width - ulen(value))
|
|
143
|
+
yield (padding + value
|
|
144
|
+
if align == "right"
|
|
145
|
+
else value + padding)
|
|
146
|
+
|
|
147
|
+
def utruncate(string, width):
|
|
148
|
+
# Truncate string to display width accounting for
|
|
149
|
+
# Unicode characters that have a display width != 1.
|
|
150
|
+
for i in range(1, len(string)):
|
|
151
|
+
if ulen(string[:i]) > width:
|
|
152
|
+
return string[:(i-1)]
|
|
153
|
+
return string
|
|
154
|
+
|
|
148
155
|
def xopen(path, mode="r", **kwargs):
|
|
149
156
|
if "b" not in mode:
|
|
150
157
|
kwargs.setdefault("encoding", "utf-8")
|
|
@@ -647,7 +647,7 @@ class Vector(np.ndarray):
|
|
|
647
647
|
if ksep is None:
|
|
648
648
|
ksep = dataiter.PRINT_THOUSAND_SEPARATOR
|
|
649
649
|
quote = util.quote if quote else identity
|
|
650
|
-
pad = util.
|
|
650
|
+
pad = util.upad if pad else identity
|
|
651
651
|
if self.is_float():
|
|
652
652
|
strings = util.format_floats(self, ksep=ksep)
|
|
653
653
|
return self.__class__.fast(pad(strings), str)
|
|
@@ -660,7 +660,7 @@ class Vector(np.ndarray):
|
|
|
660
660
|
lines = strings[i].splitlines()
|
|
661
661
|
if (util.ulen(strings[i]) > truncate_width or
|
|
662
662
|
(len(lines) > 1 and truncate_width < inf)):
|
|
663
|
-
strings[i] = lines[0]
|
|
663
|
+
strings[i] = util.utruncate(lines[0], truncate_width-1) + "…"
|
|
664
664
|
return self.__class__.fast(pad(strings), str)
|
|
665
665
|
if self.is_string():
|
|
666
666
|
strings = [quote(x) for x in self]
|
|
@@ -668,7 +668,7 @@ class Vector(np.ndarray):
|
|
|
668
668
|
lines = strings[i].splitlines()
|
|
669
669
|
if (util.ulen(strings[i]) > truncate_width or
|
|
670
670
|
(len(lines) > 1 and truncate_width < inf)):
|
|
671
|
-
strings[i] = lines[0]
|
|
671
|
+
strings[i] = util.utruncate(lines[0], truncate_width-1) + "…"
|
|
672
672
|
return self.__class__.fast(pad(strings), str)
|
|
673
673
|
strings = [str(x) for x in self]
|
|
674
674
|
return self.__class__.fast(pad(strings), str)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
2
|
+
requires = ["hatchling==1.21.1"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
@@ -10,7 +10,7 @@ readme = "README.md"
|
|
|
10
10
|
license = "MIT"
|
|
11
11
|
requires-python = ">=3.6.0"
|
|
12
12
|
authors = [{ name = "Osmo Salomaa", email = "otsaloma@iki.fi" }]
|
|
13
|
-
dependencies = ["attd>=0.3", "numpy>=1.7", "pandas>=1.0"]
|
|
13
|
+
dependencies = ["attd>=0.3", "numpy>=1.7,<2.0", "pandas>=1.0", "wcwidth>=0.1"]
|
|
14
14
|
|
|
15
15
|
[project.urls]
|
|
16
16
|
Homepage = "https://github.com/otsaloma/dataiter"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|