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.
- {dataiter-0.9999 → dataiter-1.1}/PKG-INFO +2 -2
- {dataiter-0.9999 → dataiter-1.1}/dataiter/__init__.py +1 -1
- {dataiter-0.9999 → dataiter-1.1}/dataiter/data_frame.py +63 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/dtypes.py +6 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/regex.py +1 -1
- {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_data_frame.py +28 -3
- {dataiter-0.9999 → dataiter-1.1}/pyproject.toml +2 -2
- {dataiter-0.9999 → dataiter-1.1}/.gitignore +0 -0
- {dataiter-0.9999 → dataiter-1.1}/AUTHORS.md +0 -0
- {dataiter-0.9999 → dataiter-1.1}/COPYING +0 -0
- {dataiter-0.9999 → dataiter-1.1}/README.md +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/aggregate.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/deco.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/dt.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/geojson.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/io.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/list_of_dicts.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/test/__init__.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_aggregate.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_dt.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_geojson.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_io.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_list_of_dicts.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_regex.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_util.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/test/test_vector.py +0 -0
- {dataiter-0.9999 → dataiter-1.1}/dataiter/util.py +0 -0
- {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:
|
|
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)
|
|
@@ -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,
|
|
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
|
-
#
|
|
35
|
-
|
|
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
|
|
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
|