dataiter 1.0__tar.gz → 1.1__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.1}/PKG-INFO +2 -2
- {dataiter-1.0 → dataiter-1.1}/dataiter/__init__.py +1 -1
- {dataiter-1.0 → dataiter-1.1}/dataiter/data_frame.py +63 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_data_frame.py +26 -0
- {dataiter-1.0 → dataiter-1.1}/pyproject.toml +1 -1
- {dataiter-1.0 → dataiter-1.1}/.gitignore +0 -0
- {dataiter-1.0 → dataiter-1.1}/AUTHORS.md +0 -0
- {dataiter-1.0 → dataiter-1.1}/COPYING +0 -0
- {dataiter-1.0 → dataiter-1.1}/README.md +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/aggregate.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/deco.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/dt.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/dtypes.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/geojson.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/io.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/list_of_dicts.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/regex.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/test/__init__.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_aggregate.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_dt.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_geojson.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_io.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_list_of_dicts.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_regex.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_util.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_vector.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/dataiter/util.py +0 -0
- {dataiter-1.0 → dataiter-1.1}/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.1
|
|
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
|
|
@@ -776,6 +776,69 @@ class DataFrame(dict):
|
|
|
776
776
|
def _parse_rows_from_integer(self, rows):
|
|
777
777
|
return Vector.fast(rows, int)
|
|
778
778
|
|
|
779
|
+
def pivot_longer(self, *, ids=None, names=None, values=None):
|
|
780
|
+
"""
|
|
781
|
+
Pivot data from wide to long format.
|
|
782
|
+
|
|
783
|
+
`ids` should be the name or a list of names of identifier columns. All
|
|
784
|
+
other columns are considered variable columns and will be pivoted.
|
|
785
|
+
`names` and `values` are names of columns into which the variable names
|
|
786
|
+
and values are put in the result.
|
|
787
|
+
|
|
788
|
+
>>> wide = di.read_csv("data/listings.csv")
|
|
789
|
+
>>> wide
|
|
790
|
+
>>> wide.pivot_longer(ids="id", names="name", values="value")
|
|
791
|
+
"""
|
|
792
|
+
ids = util.sequencify(ids)
|
|
793
|
+
if not all(x in self for x in ids):
|
|
794
|
+
raise ValueError(f"Bad {ids=}")
|
|
795
|
+
long = self.__class__()
|
|
796
|
+
for name in (x for x in self if x not in ids):
|
|
797
|
+
part = self.select(*ids)
|
|
798
|
+
part[names] = name
|
|
799
|
+
# Maybe we could avoid using object here,
|
|
800
|
+
# but it gets complicated.
|
|
801
|
+
part[values] = self[name].as_object()
|
|
802
|
+
long = long.rbind(part)
|
|
803
|
+
return long.sort(**dict.fromkeys(ids, 1))
|
|
804
|
+
|
|
805
|
+
def pivot_wider(self, *, ids=None, names=None, values=None, rename=None):
|
|
806
|
+
"""
|
|
807
|
+
Pivot data from long to wide format.
|
|
808
|
+
|
|
809
|
+
`ids` should be the name or a list of names of identifier columns by
|
|
810
|
+
which the result will be unique. If `ids` is not given, it defaults to
|
|
811
|
+
all columns except `names` and `values`. `names` should be the name of
|
|
812
|
+
the column that contains the names of variables, which become new
|
|
813
|
+
columns. `values` should be the name of the column that contains the
|
|
814
|
+
corresponding values. `rename` is an optional function that you can use
|
|
815
|
+
to e.g. lowercase the new column names or add a prefix or suffix.
|
|
816
|
+
|
|
817
|
+
>>> long = di.read_csv("data/downloads.csv")
|
|
818
|
+
>>> long = long.sort(date=1)
|
|
819
|
+
>>> long
|
|
820
|
+
>>> long.pivot_wider(ids="date", names="category", values="downloads", rename=str.lower)
|
|
821
|
+
"""
|
|
822
|
+
if names not in self:
|
|
823
|
+
raise ValueError(f"Bad {names=}")
|
|
824
|
+
if values not in self:
|
|
825
|
+
raise ValueError(f"Bad {values=}")
|
|
826
|
+
if not ids:
|
|
827
|
+
ids = [x for x in self if x not in [names, values]]
|
|
828
|
+
ids = util.sequencify(ids)
|
|
829
|
+
if not all(x in self for x in ids):
|
|
830
|
+
raise ValueError(f"Bad {ids=}")
|
|
831
|
+
long = self.copy()
|
|
832
|
+
wide = self.select(*ids).unique(*ids)
|
|
833
|
+
if rename:
|
|
834
|
+
long[names] = long[names].map(rename)
|
|
835
|
+
for name in long[names].unique().sort():
|
|
836
|
+
part = long.filter(long[names] == name)
|
|
837
|
+
part = part.select(*ids, values)
|
|
838
|
+
part = part.rename(**{name: values})
|
|
839
|
+
wide = wide.left_join(part, *ids)
|
|
840
|
+
return wide
|
|
841
|
+
|
|
779
842
|
def pop(self, key, *args, **kwargs):
|
|
780
843
|
""""""
|
|
781
844
|
value = super().pop(key, *args, **kwargs)
|
|
@@ -375,6 +375,32 @@ class TestDataFrame:
|
|
|
375
375
|
data = test.data_frame("vehicles.csv")
|
|
376
376
|
assert data.nrow == 33442
|
|
377
377
|
|
|
378
|
+
def test_pivot_longer(self):
|
|
379
|
+
wide = test.data_frame("listings.csv")
|
|
380
|
+
wide = wide.sort(id=1).head(10)
|
|
381
|
+
long = wide.pivot_longer(ids="id",
|
|
382
|
+
names="name",
|
|
383
|
+
values="value")
|
|
384
|
+
|
|
385
|
+
assert long.nrow == 50
|
|
386
|
+
assert long.ncol == 3
|
|
387
|
+
assert all(np.isin(long.name, wide.colnames))
|
|
388
|
+
|
|
389
|
+
def test_pivot_wider(self):
|
|
390
|
+
long = test.data_frame("downloads.csv")
|
|
391
|
+
long = long.sort(date=1).tail(100)
|
|
392
|
+
wide = long.pivot_wider(ids="date",
|
|
393
|
+
names="category",
|
|
394
|
+
values="downloads",
|
|
395
|
+
rename=lambda x: f"dl_{x.lower()}")
|
|
396
|
+
|
|
397
|
+
assert wide.nrow == 20
|
|
398
|
+
assert wide.ncol == 6
|
|
399
|
+
total_downloads = sum(
|
|
400
|
+
sum(wide[x]) for x in wide.colnames
|
|
401
|
+
if x.startswith("dl_"))
|
|
402
|
+
assert total_downloads == sum(long.downloads)
|
|
403
|
+
|
|
378
404
|
def test_pop(self):
|
|
379
405
|
data = test.data_frame("vehicles.csv")
|
|
380
406
|
data.pop("fuel")
|
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|