dataiter 1.0__tar.gz → 1.2__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-1.0 → dataiter-1.2}/.gitignore +1 -0
- {dataiter-1.0 → dataiter-1.2}/PKG-INFO +2 -2
- {dataiter-1.0 → dataiter-1.2}/dataiter/__init__.py +1 -1
- {dataiter-1.0 → dataiter-1.2}/dataiter/aggregate.py +9 -16
- {dataiter-1.0 → dataiter-1.2}/dataiter/data_frame.py +71 -3
- {dataiter-1.0 → dataiter-1.2}/dataiter/dt.py +13 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/test/test_aggregate.py +2 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/test/test_data_frame.py +35 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/test/test_dt.py +16 -0
- {dataiter-1.0 → dataiter-1.2}/pyproject.toml +1 -1
- {dataiter-1.0 → dataiter-1.2}/AUTHORS.md +0 -0
- {dataiter-1.0 → dataiter-1.2}/COPYING +0 -0
- {dataiter-1.0 → dataiter-1.2}/README.md +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/deco.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/dtypes.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/geojson.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/io.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/list_of_dicts.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/regex.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/test/__init__.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/test/test_geojson.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/test/test_io.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/test/test_list_of_dicts.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/test/test_regex.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/test/test_util.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/test/test_vector.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/util.py +0 -0
- {dataiter-1.0 → dataiter-1.2}/dataiter/vector.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dataiter
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2
|
|
4
4
|
Summary: Simple, light-weight data frames for Python
|
|
5
5
|
Project-URL: Homepage, https://github.com/otsaloma/dataiter
|
|
6
6
|
Author-email: Osmo Salomaa <otsaloma@iki.fi>
|
|
7
|
-
License
|
|
7
|
+
License: MIT
|
|
8
8
|
License-File: AUTHORS.md
|
|
9
9
|
License-File: COPYING
|
|
10
10
|
Requires-Python: >=3.9.0
|
|
@@ -25,7 +25,6 @@ import functools
|
|
|
25
25
|
import numpy as np
|
|
26
26
|
import statistics
|
|
27
27
|
|
|
28
|
-
from collections import Counter
|
|
29
28
|
from dataiter import deco
|
|
30
29
|
from dataiter import Vector
|
|
31
30
|
|
|
@@ -403,36 +402,30 @@ def mode(x, *, drop_na=True):
|
|
|
403
402
|
aggregate.group_aware = True
|
|
404
403
|
return aggregate
|
|
405
404
|
x = handle_na(x, drop_na)
|
|
406
|
-
return
|
|
405
|
+
return statistics.mode(x) if len(x) >= 1 else x.na_value
|
|
407
406
|
|
|
408
407
|
@deco.listify
|
|
409
408
|
def mode_apply(x, group, drop_na):
|
|
410
409
|
for xg in yield_groups(x, group, drop_na):
|
|
411
|
-
yield
|
|
410
|
+
yield statistics.mode(xg) if len(xg) >= 1 else None
|
|
412
411
|
|
|
413
412
|
@njit(cache=dataiter.USE_NUMBA_CACHE)
|
|
414
413
|
def mode_apply_numba(x, group, drop_na):
|
|
415
414
|
out = []
|
|
416
415
|
for xg in yield_groups_numba(x, group, drop_na):
|
|
417
416
|
if len(xg) > 0:
|
|
418
|
-
|
|
417
|
+
best_found = xg[0]
|
|
418
|
+
best_count = 1
|
|
419
419
|
for i in range(len(xg)):
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
420
|
+
count = np.sum(xg == xg[i])
|
|
421
|
+
if count > best_count:
|
|
422
|
+
best_found = xg[i]
|
|
423
|
+
best_count = count
|
|
424
|
+
out.append(best_found)
|
|
424
425
|
else:
|
|
425
426
|
out.append(None)
|
|
426
427
|
return out
|
|
427
428
|
|
|
428
|
-
def mode1(x):
|
|
429
|
-
try:
|
|
430
|
-
return statistics.mode(x)
|
|
431
|
-
except statistics.StatisticsError:
|
|
432
|
-
# Python < 3.8 with several elements tied for mode.
|
|
433
|
-
# Return the first encountered of the tied elements.
|
|
434
|
-
return Counter(x).most_common(1)[0][0]
|
|
435
|
-
|
|
436
429
|
@composite
|
|
437
430
|
def nth(x, index, *, drop_na=False):
|
|
438
431
|
"""
|
|
@@ -396,7 +396,12 @@ class DataFrame(dict):
|
|
|
396
396
|
>>> data = di.read_csv("data/listings.csv")
|
|
397
397
|
>>> data.count("hood")
|
|
398
398
|
"""
|
|
399
|
-
|
|
399
|
+
stat = self.copy().group_by(*colnames).aggregate(_n_=dataiter.count())
|
|
400
|
+
if (name := "n") in stat:
|
|
401
|
+
while name in stat: name += "_"
|
|
402
|
+
stat = stat.rename(**{name: "n"})
|
|
403
|
+
stat.n = stat.pop("_n_")
|
|
404
|
+
return stat
|
|
400
405
|
|
|
401
406
|
def deepcopy(self):
|
|
402
407
|
"""
|
|
@@ -776,6 +781,69 @@ class DataFrame(dict):
|
|
|
776
781
|
def _parse_rows_from_integer(self, rows):
|
|
777
782
|
return Vector.fast(rows, int)
|
|
778
783
|
|
|
784
|
+
def pivot_longer(self, *, ids=None, names=None, values=None):
|
|
785
|
+
"""
|
|
786
|
+
Pivot data from wide to long format.
|
|
787
|
+
|
|
788
|
+
`ids` should be the name or a list of names of identifier columns. All
|
|
789
|
+
other columns are considered variable columns and will be pivoted.
|
|
790
|
+
`names` and `values` are names of columns into which the variable names
|
|
791
|
+
and values are put in the result.
|
|
792
|
+
|
|
793
|
+
>>> wide = di.read_csv("data/listings.csv")
|
|
794
|
+
>>> wide
|
|
795
|
+
>>> wide.pivot_longer(ids="id", names="name", values="value")
|
|
796
|
+
"""
|
|
797
|
+
ids = util.sequencify(ids)
|
|
798
|
+
if not all(x in self for x in ids):
|
|
799
|
+
raise ValueError(f"Bad {ids=}")
|
|
800
|
+
long = self.__class__()
|
|
801
|
+
for name in (x for x in self if x not in ids):
|
|
802
|
+
part = self.select(*ids)
|
|
803
|
+
part[names] = name
|
|
804
|
+
# Maybe we could avoid using object here,
|
|
805
|
+
# but it gets complicated.
|
|
806
|
+
part[values] = self[name].as_object()
|
|
807
|
+
long = long.rbind(part)
|
|
808
|
+
return long.sort(**dict.fromkeys(ids, 1))
|
|
809
|
+
|
|
810
|
+
def pivot_wider(self, *, ids=None, names=None, values=None, rename=None):
|
|
811
|
+
"""
|
|
812
|
+
Pivot data from long to wide format.
|
|
813
|
+
|
|
814
|
+
`ids` should be the name or a list of names of identifier columns by
|
|
815
|
+
which the result will be unique. If `ids` is not given, it defaults to
|
|
816
|
+
all columns except `names` and `values`. `names` should be the name of
|
|
817
|
+
the column that contains the names of variables, which become new
|
|
818
|
+
columns. `values` should be the name of the column that contains the
|
|
819
|
+
corresponding values. `rename` is an optional function that you can use
|
|
820
|
+
to e.g. lowercase the new column names or add a prefix or suffix.
|
|
821
|
+
|
|
822
|
+
>>> long = di.read_csv("data/downloads.csv")
|
|
823
|
+
>>> long = long.sort(date=1)
|
|
824
|
+
>>> long
|
|
825
|
+
>>> long.pivot_wider(ids="date", names="category", values="downloads", rename=str.lower)
|
|
826
|
+
"""
|
|
827
|
+
if names not in self:
|
|
828
|
+
raise ValueError(f"Bad {names=}")
|
|
829
|
+
if values not in self:
|
|
830
|
+
raise ValueError(f"Bad {values=}")
|
|
831
|
+
if not ids:
|
|
832
|
+
ids = [x for x in self if x not in [names, values]]
|
|
833
|
+
ids = util.sequencify(ids)
|
|
834
|
+
if not all(x in self for x in ids):
|
|
835
|
+
raise ValueError(f"Bad {ids=}")
|
|
836
|
+
long = self.copy()
|
|
837
|
+
wide = self.select(*ids).unique(*ids)
|
|
838
|
+
if rename:
|
|
839
|
+
long[names] = long[names].map(rename)
|
|
840
|
+
for name in long[names].unique().sort():
|
|
841
|
+
part = long.filter(long[names] == name)
|
|
842
|
+
part = part.select(*ids, values)
|
|
843
|
+
part = part.rename(**{name: values})
|
|
844
|
+
wide = wide.left_join(part, *ids)
|
|
845
|
+
return wide
|
|
846
|
+
|
|
779
847
|
def pop(self, key, *args, **kwargs):
|
|
780
848
|
""""""
|
|
781
849
|
value = super().pop(key, *args, **kwargs)
|
|
@@ -1195,9 +1263,9 @@ class DataFrame(dict):
|
|
|
1195
1263
|
batch_rows[i] += " "
|
|
1196
1264
|
batch_rows[i] += column[i]
|
|
1197
1265
|
del columns[colname]
|
|
1198
|
-
rows_to_print.append("" if rows_to_print else "
|
|
1266
|
+
rows_to_print.append("" if rows_to_print else "")
|
|
1199
1267
|
rows_to_print += batch_rows
|
|
1200
|
-
rows_to_print.append("
|
|
1268
|
+
rows_to_print.append("")
|
|
1201
1269
|
if max_rows < self.nrow:
|
|
1202
1270
|
rows_to_print.append(f"... {self.nrow} rows total")
|
|
1203
1271
|
return "\n".join(rows_to_print)
|
|
@@ -74,6 +74,19 @@ def hour(x):
|
|
|
74
74
|
"""
|
|
75
75
|
return _pull_int(x, lambda y: y.hour)
|
|
76
76
|
|
|
77
|
+
def isoformat(x):
|
|
78
|
+
"""
|
|
79
|
+
Return datetime `x` as a string in ISO 8601 format.
|
|
80
|
+
|
|
81
|
+
For dates, return `YYYY-MM-DD`, for datetimes `YYYY-MM-DDTHH:MM:SS.ffffff`.
|
|
82
|
+
|
|
83
|
+
>>> x = dt.new([dt.today()])
|
|
84
|
+
>>> dt.isoformat(x)
|
|
85
|
+
>>> y = dt.new([dt.now()])
|
|
86
|
+
>>> dt.isoformat(y)
|
|
87
|
+
"""
|
|
88
|
+
return _pull_str(x, lambda x: x.isoformat())
|
|
89
|
+
|
|
77
90
|
def isoweek(x):
|
|
78
91
|
"""
|
|
79
92
|
Extract ISO 8601 week from datetime `x`.
|
|
@@ -229,6 +229,15 @@ class TestDataFrame:
|
|
|
229
229
|
assert stat.hood.equal(data.hood.unique().sort())
|
|
230
230
|
assert stat.n.sum() == data.nrow
|
|
231
231
|
|
|
232
|
+
def test_count_n_exists(self):
|
|
233
|
+
data = test.data_frame("listings.csv")
|
|
234
|
+
stat1 = data.count("zipcode")
|
|
235
|
+
stat2 = data.count("zipcode").count("n")
|
|
236
|
+
assert stat1.colnames == ["zipcode", "n"]
|
|
237
|
+
assert stat2.colnames == ["n_", "n"]
|
|
238
|
+
assert stat2.nrow == len(stat1.n.unique())
|
|
239
|
+
assert sum(stat2.n) == stat1.nrow
|
|
240
|
+
|
|
232
241
|
def test_deepcopy(self):
|
|
233
242
|
orig = test.data_frame("vehicles.csv")
|
|
234
243
|
data = orig.copy()
|
|
@@ -375,6 +384,32 @@ class TestDataFrame:
|
|
|
375
384
|
data = test.data_frame("vehicles.csv")
|
|
376
385
|
assert data.nrow == 33442
|
|
377
386
|
|
|
387
|
+
def test_pivot_longer(self):
|
|
388
|
+
wide = test.data_frame("listings.csv")
|
|
389
|
+
wide = wide.sort(id=1).head(10)
|
|
390
|
+
long = wide.pivot_longer(ids="id",
|
|
391
|
+
names="name",
|
|
392
|
+
values="value")
|
|
393
|
+
|
|
394
|
+
assert long.nrow == 50
|
|
395
|
+
assert long.ncol == 3
|
|
396
|
+
assert all(np.isin(long.name, wide.colnames))
|
|
397
|
+
|
|
398
|
+
def test_pivot_wider(self):
|
|
399
|
+
long = test.data_frame("downloads.csv")
|
|
400
|
+
long = long.sort(date=1).tail(100)
|
|
401
|
+
wide = long.pivot_wider(ids="date",
|
|
402
|
+
names="category",
|
|
403
|
+
values="downloads",
|
|
404
|
+
rename=lambda x: f"dl_{x.lower()}")
|
|
405
|
+
|
|
406
|
+
assert wide.nrow == 20
|
|
407
|
+
assert wide.ncol == 6
|
|
408
|
+
total_downloads = sum(
|
|
409
|
+
sum(wide[x]) for x in wide.colnames
|
|
410
|
+
if x.startswith("dl_"))
|
|
411
|
+
assert total_downloads == sum(long.downloads)
|
|
412
|
+
|
|
378
413
|
def test_pop(self):
|
|
379
414
|
data = test.data_frame("vehicles.csv")
|
|
380
415
|
data.pop("fuel")
|
|
@@ -62,6 +62,22 @@ class TestDT:
|
|
|
62
62
|
a = dt.new(["2022-10-15T12:34:56", NaT])
|
|
63
63
|
assert dt.hour(a).tolist() == [12, None]
|
|
64
64
|
|
|
65
|
+
def test_isoformat_date(self):
|
|
66
|
+
a = np.array(["2022-10-15", NaT], np.datetime64)
|
|
67
|
+
assert dt.isoformat(a).tolist() == ["2022-10-15", None]
|
|
68
|
+
|
|
69
|
+
def test_isoformat_date_scalar(self):
|
|
70
|
+
x = np.datetime64("2022-10-15")
|
|
71
|
+
assert dt.isoformat(x) == "2022-10-15"
|
|
72
|
+
|
|
73
|
+
def test_isoformat_datetime(self):
|
|
74
|
+
a = np.array(["2022-10-15T12:34:56.789", NaT], np.datetime64)
|
|
75
|
+
assert dt.isoformat(a).tolist() == ["2022-10-15T12:34:56.789000", None]
|
|
76
|
+
|
|
77
|
+
def test_isoformat_datetime_scalar(self):
|
|
78
|
+
x = np.datetime64("2022-10-15T12:34:56.789")
|
|
79
|
+
assert dt.isoformat(x) == "2022-10-15T12:34:56.789000"
|
|
80
|
+
|
|
65
81
|
def test_isoweek(self):
|
|
66
82
|
a = dt.new(["2022-10-15", NaT])
|
|
67
83
|
assert dt.isoweek(a).tolist() == [41, None]
|
|
@@ -7,7 +7,7 @@ name = "dataiter"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Simple, light-weight data frames for Python"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
license = "MIT"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
11
|
requires-python = ">=3.9.0"
|
|
12
12
|
authors = [{ name = "Osmo Salomaa", email = "otsaloma@iki.fi" }]
|
|
13
13
|
dependencies = ["attd>=0.3", "numpy>=2.0,<3.0", "pyarrow>=2.0", "wcwidth>=0.1"]
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|