dataiter 0.9999__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-0.9999 → dataiter-1.1}/PKG-INFO +2 -2
  2. {dataiter-0.9999 → dataiter-1.1}/dataiter/__init__.py +1 -1
  3. {dataiter-0.9999 → dataiter-1.1}/dataiter/data_frame.py +63 -0
  4. {dataiter-0.9999 → dataiter-1.1}/dataiter/dtypes.py +6 -0
  5. {dataiter-0.9999 → dataiter-1.1}/dataiter/regex.py +1 -1
  6. {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_data_frame.py +28 -3
  7. {dataiter-0.9999 → dataiter-1.1}/pyproject.toml +2 -2
  8. {dataiter-0.9999 → dataiter-1.1}/.gitignore +0 -0
  9. {dataiter-0.9999 → dataiter-1.1}/AUTHORS.md +0 -0
  10. {dataiter-0.9999 → dataiter-1.1}/COPYING +0 -0
  11. {dataiter-0.9999 → dataiter-1.1}/README.md +0 -0
  12. {dataiter-0.9999 → dataiter-1.1}/dataiter/aggregate.py +0 -0
  13. {dataiter-0.9999 → dataiter-1.1}/dataiter/deco.py +0 -0
  14. {dataiter-0.9999 → dataiter-1.1}/dataiter/dt.py +0 -0
  15. {dataiter-0.9999 → dataiter-1.1}/dataiter/geojson.py +0 -0
  16. {dataiter-0.9999 → dataiter-1.1}/dataiter/io.py +0 -0
  17. {dataiter-0.9999 → dataiter-1.1}/dataiter/list_of_dicts.py +0 -0
  18. {dataiter-0.9999 → dataiter-1.1}/dataiter/test/__init__.py +0 -0
  19. {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_aggregate.py +0 -0
  20. {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_dt.py +0 -0
  21. {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_geojson.py +0 -0
  22. {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_io.py +0 -0
  23. {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_list_of_dicts.py +0 -0
  24. {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_regex.py +0 -0
  25. {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_util.py +0 -0
  26. {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_vector.py +0 -0
  27. {dataiter-0.9999 → dataiter-1.1}/dataiter/util.py +0 -0
  28. {dataiter-0.9999 → dataiter-1.1}/dataiter/vector.py +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dataiter
3
- Version: 0.9999
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__ = "0.9999"
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)
@@ -20,6 +20,8 @@
20
20
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  # THE SOFTWARE.
22
22
 
23
+ import warnings
24
+
23
25
  from numpy.dtypes import StringDType
24
26
 
25
27
  #: Instance of NumPy variable-width StringDType used
@@ -30,3 +32,7 @@ string = StringDType(na_object="")
30
32
  # input, a distinction between NA and blank cannot usually be made.
31
33
  # TODO: Consider changing this to something like ':NA:'.
32
34
  # https://numpy.org/doc/stable/user/basics.strings.html#missing-data-support
35
+
36
+ # Ignore pointless warnings about using StringDType in numpy.savez.
37
+ _pattern = "Custom dtypes are saved as python objects using the pickle protocol"
38
+ warnings.filterwarnings("ignore", message=_pattern, category=UserWarning)
@@ -129,7 +129,7 @@ def sub(pattern, repl, string, count=0, flags=0):
129
129
  out, na = _prep(string, dtypes.string, dtypes.string.na_object)
130
130
  for i in np.flatnonzero(~na):
131
131
  out[i] = re.sub(pattern, repl, string[i], count=count, flags=flags)
132
- return Vector.fast(out, dtypes.string)
132
+ return Vector.fast(out, str)
133
133
 
134
134
  def subn(pattern, repl, string, count=0, flags=0):
135
135
  """
@@ -31,9 +31,8 @@ from dataiter import test
31
31
  from pathlib import Path
32
32
  from unittest.mock import patch
33
33
 
34
- # This UserWarning seems unavoidable when using StringDtype, which unlike
35
- # other dtypes must be instantiated and that custom instance used.
36
- IGNORE_NPZ_PICKLE_WARNING = "ignore:Custom dtypes are saved as python objects using the pickle protocol."
34
+ # See dataiter/dtypes.py for the equivalent non-test ignore.
35
+ IGNORE_NPZ_PICKLE_WARNING = "ignore:Custom dtypes are saved as python objects using the pickle protocol"
37
36
 
38
37
  class TestDataFrameColumn:
39
38
 
@@ -376,6 +375,32 @@ class TestDataFrame:
376
375
  data = test.data_frame("vehicles.csv")
377
376
  assert data.nrow == 33442
378
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
+
379
404
  def test_pop(self):
380
405
  data = test.data_frame("vehicles.csv")
381
406
  data.pop("fuel")
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["hatchling==1.21.1"]
2
+ requires = ["build==1.2.2.post1", "hatchling==1.21.1"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
@@ -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