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.
Files changed (28) hide show
  1. {dataiter-1.0 → dataiter-1.1}/PKG-INFO +2 -2
  2. {dataiter-1.0 → dataiter-1.1}/dataiter/__init__.py +1 -1
  3. {dataiter-1.0 → dataiter-1.1}/dataiter/data_frame.py +63 -0
  4. {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_data_frame.py +26 -0
  5. {dataiter-1.0 → dataiter-1.1}/pyproject.toml +1 -1
  6. {dataiter-1.0 → dataiter-1.1}/.gitignore +0 -0
  7. {dataiter-1.0 → dataiter-1.1}/AUTHORS.md +0 -0
  8. {dataiter-1.0 → dataiter-1.1}/COPYING +0 -0
  9. {dataiter-1.0 → dataiter-1.1}/README.md +0 -0
  10. {dataiter-1.0 → dataiter-1.1}/dataiter/aggregate.py +0 -0
  11. {dataiter-1.0 → dataiter-1.1}/dataiter/deco.py +0 -0
  12. {dataiter-1.0 → dataiter-1.1}/dataiter/dt.py +0 -0
  13. {dataiter-1.0 → dataiter-1.1}/dataiter/dtypes.py +0 -0
  14. {dataiter-1.0 → dataiter-1.1}/dataiter/geojson.py +0 -0
  15. {dataiter-1.0 → dataiter-1.1}/dataiter/io.py +0 -0
  16. {dataiter-1.0 → dataiter-1.1}/dataiter/list_of_dicts.py +0 -0
  17. {dataiter-1.0 → dataiter-1.1}/dataiter/regex.py +0 -0
  18. {dataiter-1.0 → dataiter-1.1}/dataiter/test/__init__.py +0 -0
  19. {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_aggregate.py +0 -0
  20. {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_dt.py +0 -0
  21. {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_geojson.py +0 -0
  22. {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_io.py +0 -0
  23. {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_list_of_dicts.py +0 -0
  24. {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_regex.py +0 -0
  25. {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_util.py +0 -0
  26. {dataiter-1.0 → dataiter-1.1}/dataiter/test/test_vector.py +0 -0
  27. {dataiter-1.0 → dataiter-1.1}/dataiter/util.py +0 -0
  28. {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.0
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-Expression: MIT
7
+ License: MIT
8
8
  License-File: AUTHORS.md
9
9
  License-File: COPYING
10
10
  Requires-Python: >=3.9.0
@@ -25,7 +25,7 @@ import numpy as np
25
25
 
26
26
  from dataiter import util
27
27
 
28
- __version__ = "1.0"
28
+ __version__ = "1.1"
29
29
 
30
30
  DEFAULT_PEEK_ELEMENTS = 10
31
31
  DEFAULT_PEEK_ITEMS = 3
@@ -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