dataiter 1.3.1__tar.gz → 1.4__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.3.1 → dataiter-1.4}/.gitignore +1 -4
- {dataiter-1.3.1 → dataiter-1.4}/PKG-INFO +13 -6
- {dataiter-1.3.1 → dataiter-1.4}/README.md +9 -2
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/__init__.py +32 -32
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/aggregate.py +0 -1
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/data_frame.py +51 -82
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/io.py +1 -1
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/list_of_dicts.py +1 -1
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/regex.py +21 -45
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/test/test_aggregate.py +1 -1
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/test/test_data_frame.py +44 -43
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/test/test_dt.py +1 -1
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/test/test_geojson.py +4 -6
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/test/test_list_of_dicts.py +16 -17
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/test/test_util.py +8 -9
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/test/test_vector.py +1 -1
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/util.py +4 -5
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/vector.py +16 -7
- {dataiter-1.3.1 → dataiter-1.4}/pyproject.toml +3 -3
- {dataiter-1.3.1 → dataiter-1.4}/AUTHORS.md +0 -0
- {dataiter-1.3.1 → dataiter-1.4}/COPYING +0 -0
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/deco.py +0 -0
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/dt.py +0 -0
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/dtypes.py +0 -0
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/geojson.py +0 -0
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/test/__init__.py +0 -0
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/test/test_io.py +0 -0
- {dataiter-1.3.1 → dataiter-1.4}/dataiter/test/test_regex.py +0 -0
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
*.prof
|
|
3
|
-
.claude
|
|
1
|
+
.claude/settings.local.json
|
|
4
2
|
.env
|
|
5
3
|
.envrc
|
|
6
4
|
.pytest_cache
|
|
@@ -9,7 +7,6 @@ benchmark-head.py
|
|
|
9
7
|
benchmark-versions.csv
|
|
10
8
|
benchmark-versions.ods
|
|
11
9
|
build
|
|
12
|
-
dataiter.egg-info
|
|
13
10
|
dist
|
|
14
11
|
doc/_build
|
|
15
12
|
doc/comparison/comparison.html
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: dataiter
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4
|
|
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: MIT
|
|
7
|
+
License-Expression: MIT
|
|
8
8
|
License-File: AUTHORS.md
|
|
9
9
|
License-File: COPYING
|
|
10
10
|
Requires-Python: >=3.11.0
|
|
11
11
|
Requires-Dist: attd>=0.3
|
|
12
12
|
Requires-Dist: numpy<3.0,>=2.4.0
|
|
13
|
-
Requires-Dist: pyarrow>=
|
|
13
|
+
Requires-Dist: pyarrow>=16.0
|
|
14
14
|
Requires-Dist: wcwidth>=0.1
|
|
15
15
|
Description-Content-Type: text/markdown
|
|
16
16
|
|
|
17
|
-
Simple, Light-Weight Data Frames for Python
|
|
18
|
-
===========================================
|
|
17
|
+
# Simple, Light-Weight Data Frames for Python
|
|
19
18
|
|
|
20
19
|
[](https://pypi.org/project/dataiter)
|
|
21
20
|
[](https://pepy.tech/project/dataiter)
|
|
@@ -81,6 +80,14 @@ the differences and similarities in common operations.
|
|
|
81
80
|
|
|
82
81
|
https://dataiter.readthedocs.io/en/stable/comparison.html
|
|
83
82
|
|
|
83
|
+
For coding agents and LLMs, each documentation page is also available as
|
|
84
|
+
Markdown by replacing `.html` with `.md` in the URL. There's also an
|
|
85
|
+
`llms.txt` index and complete documentation as `llms-full.txt`. See
|
|
86
|
+
|
|
87
|
+
- https://dataiter.readthedocs.io/en/stable/data-frame.md
|
|
88
|
+
- https://dataiter.readthedocs.io/en/stable/llms.txt
|
|
89
|
+
- https://dataiter.readthedocs.io/en/stable/llms-full.txt
|
|
90
|
+
|
|
84
91
|
## Development
|
|
85
92
|
|
|
86
93
|
To install a virtualenv for development, use
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
Simple, Light-Weight Data Frames for Python
|
|
2
|
-
===========================================
|
|
1
|
+
# Simple, Light-Weight Data Frames for Python
|
|
3
2
|
|
|
4
3
|
[](https://pypi.org/project/dataiter)
|
|
5
4
|
[](https://pepy.tech/project/dataiter)
|
|
@@ -65,6 +64,14 @@ the differences and similarities in common operations.
|
|
|
65
64
|
|
|
66
65
|
https://dataiter.readthedocs.io/en/stable/comparison.html
|
|
67
66
|
|
|
67
|
+
For coding agents and LLMs, each documentation page is also available as
|
|
68
|
+
Markdown by replacing `.html` with `.md` in the URL. There's also an
|
|
69
|
+
`llms.txt` index and complete documentation as `llms-full.txt`. See
|
|
70
|
+
|
|
71
|
+
- https://dataiter.readthedocs.io/en/stable/data-frame.md
|
|
72
|
+
- https://dataiter.readthedocs.io/en/stable/llms.txt
|
|
73
|
+
- https://dataiter.readthedocs.io/en/stable/llms-full.txt
|
|
74
|
+
|
|
68
75
|
## Development
|
|
69
76
|
|
|
70
77
|
To install a virtualenv for development, use
|
|
@@ -25,7 +25,7 @@ import numpy as np
|
|
|
25
25
|
|
|
26
26
|
from dataiter import util
|
|
27
27
|
|
|
28
|
-
__version__ = "1.
|
|
28
|
+
__version__ = "1.4"
|
|
29
29
|
|
|
30
30
|
DEFAULT_PEEK_ELEMENTS = 10
|
|
31
31
|
DEFAULT_PEEK_ITEMS = 3
|
|
@@ -87,34 +87,34 @@ globals().pop("np", None)
|
|
|
87
87
|
globals().pop("numba", None)
|
|
88
88
|
globals().pop("util", None)
|
|
89
89
|
|
|
90
|
-
from dataiter import dtypes
|
|
91
|
-
from dataiter.vector import Vector
|
|
92
|
-
from dataiter.data_frame import DataFrame
|
|
93
|
-
from dataiter.data_frame import DataFrameColumn
|
|
94
|
-
from dataiter.geojson import GeoJSON
|
|
95
|
-
from dataiter.list_of_dicts import ListOfDicts
|
|
96
|
-
from dataiter import dt
|
|
97
|
-
from dataiter import regex
|
|
98
|
-
|
|
99
|
-
from dataiter.aggregate import all
|
|
100
|
-
from dataiter.aggregate import any
|
|
101
|
-
from dataiter.aggregate import count
|
|
102
|
-
from dataiter.aggregate import count_unique
|
|
103
|
-
from dataiter.aggregate import first
|
|
104
|
-
from dataiter.aggregate import last
|
|
105
|
-
from dataiter.aggregate import max
|
|
106
|
-
from dataiter.aggregate import mean
|
|
107
|
-
from dataiter.aggregate import median
|
|
108
|
-
from dataiter.aggregate import min
|
|
109
|
-
from dataiter.aggregate import mode
|
|
110
|
-
from dataiter.aggregate import nth
|
|
111
|
-
from dataiter.aggregate import quantile
|
|
112
|
-
from dataiter.aggregate import std
|
|
113
|
-
from dataiter.aggregate import sum
|
|
114
|
-
from dataiter.aggregate import var
|
|
115
|
-
|
|
116
|
-
from dataiter.io import read_csv
|
|
117
|
-
from dataiter.io import read_geojson
|
|
118
|
-
from dataiter.io import read_json
|
|
119
|
-
from dataiter.io import read_npz
|
|
120
|
-
from dataiter.io import read_parquet
|
|
90
|
+
from dataiter import dtypes
|
|
91
|
+
from dataiter.vector import Vector
|
|
92
|
+
from dataiter.data_frame import DataFrame
|
|
93
|
+
from dataiter.data_frame import DataFrameColumn
|
|
94
|
+
from dataiter.geojson import GeoJSON
|
|
95
|
+
from dataiter.list_of_dicts import ListOfDicts
|
|
96
|
+
from dataiter import dt
|
|
97
|
+
from dataiter import regex
|
|
98
|
+
|
|
99
|
+
from dataiter.aggregate import all
|
|
100
|
+
from dataiter.aggregate import any
|
|
101
|
+
from dataiter.aggregate import count
|
|
102
|
+
from dataiter.aggregate import count_unique
|
|
103
|
+
from dataiter.aggregate import first
|
|
104
|
+
from dataiter.aggregate import last
|
|
105
|
+
from dataiter.aggregate import max
|
|
106
|
+
from dataiter.aggregate import mean
|
|
107
|
+
from dataiter.aggregate import median
|
|
108
|
+
from dataiter.aggregate import min
|
|
109
|
+
from dataiter.aggregate import mode
|
|
110
|
+
from dataiter.aggregate import nth
|
|
111
|
+
from dataiter.aggregate import quantile
|
|
112
|
+
from dataiter.aggregate import std
|
|
113
|
+
from dataiter.aggregate import sum
|
|
114
|
+
from dataiter.aggregate import var
|
|
115
|
+
|
|
116
|
+
from dataiter.io import read_csv
|
|
117
|
+
from dataiter.io import read_geojson
|
|
118
|
+
from dataiter.io import read_json
|
|
119
|
+
from dataiter.io import read_npz
|
|
120
|
+
from dataiter.io import read_parquet
|
|
@@ -22,7 +22,6 @@
|
|
|
22
22
|
|
|
23
23
|
import codecs
|
|
24
24
|
import dataiter
|
|
25
|
-
import functools
|
|
26
25
|
import itertools
|
|
27
26
|
import json
|
|
28
27
|
import numpy as np
|
|
@@ -103,10 +102,6 @@ class DataFrame(dict):
|
|
|
103
102
|
# List of names that are actual attributes, not columns
|
|
104
103
|
ATTRIBUTES = ["colnames", "_group_colnames"]
|
|
105
104
|
|
|
106
|
-
# Use dummy attributes corresponding to dictionary keys so that
|
|
107
|
-
# Tab completion of column names at a Python shell would work.
|
|
108
|
-
COLUMN_PLACEHOLDER = type("COLUMN_PLACEHOLDER", (), {})
|
|
109
|
-
|
|
110
105
|
def __init__(self, *args, **kwargs):
|
|
111
106
|
"""
|
|
112
107
|
Return a new data frame.
|
|
@@ -122,9 +117,6 @@ class DataFrame(dict):
|
|
|
122
117
|
value.nrow == nrow): continue
|
|
123
118
|
column = DataFrameColumn(value, nrow=nrow)
|
|
124
119
|
super().__setitem__(key, column)
|
|
125
|
-
for key in self:
|
|
126
|
-
if not self.__hasattr(key) and key.isidentifier():
|
|
127
|
-
super().__setattr__(key, self.COLUMN_PLACEHOLDER)
|
|
128
120
|
# Check that we have a uniform table.
|
|
129
121
|
self._check_dimensions()
|
|
130
122
|
self._group_colnames = ()
|
|
@@ -140,12 +132,13 @@ class DataFrame(dict):
|
|
|
140
132
|
return self.__delitem__(name)
|
|
141
133
|
return super().__delattr__(name)
|
|
142
134
|
|
|
143
|
-
def
|
|
144
|
-
#
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
135
|
+
def __dir__(self):
|
|
136
|
+
# Used for tab completion of column names at a Python shell.
|
|
137
|
+
names = super().__dir__()
|
|
138
|
+
return names + [x for x in self if
|
|
139
|
+
isinstance(x, str) and
|
|
140
|
+
x.isidentifier() and
|
|
141
|
+
x not in names]
|
|
149
142
|
|
|
150
143
|
def __eq__(self, other):
|
|
151
144
|
return (isinstance(other, DataFrame) and
|
|
@@ -159,27 +152,6 @@ class DataFrame(dict):
|
|
|
159
152
|
return self.__getitem__(name)
|
|
160
153
|
raise AttributeError(name)
|
|
161
154
|
|
|
162
|
-
def __getattribute__(self, name):
|
|
163
|
-
value = super().__getattribute__(name)
|
|
164
|
-
if name == "COLUMN_PLACEHOLDER":
|
|
165
|
-
return value
|
|
166
|
-
if value is self.COLUMN_PLACEHOLDER and name in self:
|
|
167
|
-
return self[name]
|
|
168
|
-
return value
|
|
169
|
-
|
|
170
|
-
def __hasattr(self, name):
|
|
171
|
-
# Return True if attribute exists and is not a column.
|
|
172
|
-
return hasattr(self, name) and not isinstance(getattr(self, name), DataFrameColumn)
|
|
173
|
-
|
|
174
|
-
@classmethod
|
|
175
|
-
def __is_builtin_attr(cls, name):
|
|
176
|
-
return name in cls.__list_builtin_attrs()
|
|
177
|
-
|
|
178
|
-
@classmethod
|
|
179
|
-
@functools.lru_cache(None)
|
|
180
|
-
def __list_builtin_attrs(cls):
|
|
181
|
-
return set(dir(cls()))
|
|
182
|
-
|
|
183
155
|
def __setattr__(self, name, value):
|
|
184
156
|
if name in self.ATTRIBUTES:
|
|
185
157
|
return super().__setattr__(name, value)
|
|
@@ -187,8 +159,6 @@ class DataFrame(dict):
|
|
|
187
159
|
|
|
188
160
|
def __setitem__(self, key, value):
|
|
189
161
|
value = self._reconcile_column(value)
|
|
190
|
-
if not self.__hasattr(key) and key.isidentifier():
|
|
191
|
-
super().__setattr__(key, self.COLUMN_PLACEHOLDER)
|
|
192
162
|
return super().__setitem__(key, value)
|
|
193
163
|
|
|
194
164
|
def __repr__(self):
|
|
@@ -220,13 +190,12 @@ class DataFrame(dict):
|
|
|
220
190
|
group_colnames = self._group_colnames
|
|
221
191
|
data = self.sort(**dict.fromkeys(group_colnames, 1))
|
|
222
192
|
data._index_ = np.arange(data.nrow)
|
|
223
|
-
|
|
224
|
-
|
|
193
|
+
starts = data._get_group_starts(*group_colnames)
|
|
194
|
+
stat = data.select("_index_", *group_colnames).slice(starts)
|
|
225
195
|
group_aware = [getattr(x, "group_aware", False) for x in colname_function_pairs.values()]
|
|
226
196
|
if any(group_aware):
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
data._group_ = np.repeat(groups, n)
|
|
197
|
+
n = np.diff(starts, append=data.nrow)
|
|
198
|
+
data._group_ = np.repeat(np.arange(len(starts)), n)
|
|
230
199
|
slices = None
|
|
231
200
|
for colname, function in colname_function_pairs.items():
|
|
232
201
|
if getattr(function, "group_aware", False):
|
|
@@ -246,6 +215,7 @@ class DataFrame(dict):
|
|
|
246
215
|
# what special values to expect and thus we end up
|
|
247
216
|
# needing to use the slow Vector.__init__.
|
|
248
217
|
if slices is None:
|
|
218
|
+
indices = np.split(data._index_, starts[1:])
|
|
249
219
|
slices = [data._view_rows(x) for x in indices]
|
|
250
220
|
stat[colname] = [function(x) for x in slices]
|
|
251
221
|
return stat.unselect("_index_", "_group_")
|
|
@@ -589,6 +559,16 @@ class DataFrame(dict):
|
|
|
589
559
|
ba[item[0]] = ba.pop(item[1])
|
|
590
560
|
return ab.rbind(ba).sort(_aid_=1, _bid_=1).unselect("_aid_", "_bid_")
|
|
591
561
|
|
|
562
|
+
def _get_group_starts(self, *colnames):
|
|
563
|
+
# Assumes data is sorted by colnames, i.e. groups are contiguous.
|
|
564
|
+
change = np.zeros(self.nrow, bool)
|
|
565
|
+
change[:1] = True
|
|
566
|
+
for colname in colnames:
|
|
567
|
+
column = self[colname]
|
|
568
|
+
na = column.is_na()
|
|
569
|
+
change[1:] |= (column[1:] != column[:-1]) & ~(na[1:] & na[:-1])
|
|
570
|
+
return np.flatnonzero(change)
|
|
571
|
+
|
|
592
572
|
def _get_join_indices(self, other, by1, by2):
|
|
593
573
|
other_ids = list(zip(*[other[x] for x in by2]))
|
|
594
574
|
other_by_id = {other_ids[i]: i for i in range(other.nrow)}
|
|
@@ -688,7 +668,7 @@ class DataFrame(dict):
|
|
|
688
668
|
vectorized form.
|
|
689
669
|
|
|
690
670
|
>>> data = di.read_csv("data/listings-reviews.csv")
|
|
691
|
-
>>> data.map(lambda x, i: (x.reviews[i], x.rating[i]))
|
|
671
|
+
>>> data.map(lambda x, i: (x.reviews[i], x.rating[i]))[:3]
|
|
692
672
|
"""
|
|
693
673
|
return [function(self, i) for i in range(self.nrow)]
|
|
694
674
|
|
|
@@ -844,22 +824,6 @@ class DataFrame(dict):
|
|
|
844
824
|
wide = wide.left_join(part, *ids)
|
|
845
825
|
return wide
|
|
846
826
|
|
|
847
|
-
def pop(self, key, *args, **kwargs):
|
|
848
|
-
""""""
|
|
849
|
-
value = super().pop(key, *args, **kwargs)
|
|
850
|
-
if hasattr(self, key):
|
|
851
|
-
if not self.__is_builtin_attr(key):
|
|
852
|
-
super().__delattr__(key)
|
|
853
|
-
return value
|
|
854
|
-
|
|
855
|
-
def popitem(self):
|
|
856
|
-
""""""
|
|
857
|
-
key, value = super().popitem()
|
|
858
|
-
if hasattr(self, key):
|
|
859
|
-
if not self.__is_builtin_attr(key):
|
|
860
|
-
super().__delattr__(key)
|
|
861
|
-
return key, value
|
|
862
|
-
|
|
863
827
|
def print_(self, *, max_rows=None, max_width=None, truncate_width=None):
|
|
864
828
|
"""
|
|
865
829
|
Print data frame to ``sys.stdout``.
|
|
@@ -1066,7 +1030,7 @@ class DataFrame(dict):
|
|
|
1066
1030
|
>>> listings.semi_join(reviews, "id")
|
|
1067
1031
|
"""
|
|
1068
1032
|
by1, by2 = self._split_join_by(*by)
|
|
1069
|
-
other = other.unique(*by2)
|
|
1033
|
+
other = other.drop_na(*by2).unique(*by2)
|
|
1070
1034
|
found, src = self._get_join_indices(other, by1, by2)
|
|
1071
1035
|
for colname, column in self.items():
|
|
1072
1036
|
yield colname, column[found].copy()
|
|
@@ -1129,18 +1093,8 @@ class DataFrame(dict):
|
|
|
1129
1093
|
column = self[colname]
|
|
1130
1094
|
if column._is_string_fixed():
|
|
1131
1095
|
column[column.is_na()] = "\uffff"
|
|
1132
|
-
if dir > 0 and
|
|
1133
|
-
column
|
|
1134
|
-
column.is_boolean(),
|
|
1135
|
-
column.is_bytes(),
|
|
1136
|
-
column.is_datetime(),
|
|
1137
|
-
column.is_float(),
|
|
1138
|
-
column.is_integer(),
|
|
1139
|
-
# Segfaults on NumPy < 2.2.1!
|
|
1140
|
-
# https://github.com/numpy/numpy/issues/27984
|
|
1141
|
-
column.is_string(),
|
|
1142
|
-
column.is_timedelta(),
|
|
1143
|
-
)): return column
|
|
1096
|
+
if dir > 0 and not column.is_object():
|
|
1097
|
+
return column
|
|
1144
1098
|
if not column.is_number():
|
|
1145
1099
|
column = column.rank(method="min")
|
|
1146
1100
|
return column if dir > 0 else -column
|
|
@@ -1159,9 +1113,8 @@ class DataFrame(dict):
|
|
|
1159
1113
|
data = self.select(*by)
|
|
1160
1114
|
data._index_ = np.arange(data.nrow)
|
|
1161
1115
|
data = data.sort(**dict.fromkeys(by, 1))
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
return np.split(data._index_, stat._sorted_index_[1:])
|
|
1116
|
+
starts = data._get_group_starts(*by)
|
|
1117
|
+
return np.split(data._index_, starts[1:])
|
|
1165
1118
|
|
|
1166
1119
|
def _split_join_by(self, *by):
|
|
1167
1120
|
by1 = [x if isinstance(x, str) else x[0] for x in by]
|
|
@@ -1277,28 +1230,44 @@ class DataFrame(dict):
|
|
|
1277
1230
|
>>> data = di.read_csv("data/listings.csv")
|
|
1278
1231
|
>>> data.unique("hood")
|
|
1279
1232
|
"""
|
|
1280
|
-
# Strangely, standard Python is a lot faster here than np.unique
|
|
1281
|
-
# with all the extra needed across columns of different type.
|
|
1282
|
-
# We just need to avoid NaN and NaT for the 'in' checks to work.
|
|
1283
1233
|
colnames = colnames or self.colnames
|
|
1284
1234
|
columns = [self[x] for x in colnames]
|
|
1235
|
+
if all(x.is_number() or
|
|
1236
|
+
x.is_boolean() or
|
|
1237
|
+
x.is_datetime() or
|
|
1238
|
+
x.is_timedelta() for x in columns):
|
|
1239
|
+
# np.lexsort is stable, so the first row of each group
|
|
1240
|
+
# in sorted order is the first occurrence in self.
|
|
1241
|
+
order = np.lexsort(columns[::-1])
|
|
1242
|
+
starts = self.select(*colnames).slice(order)._get_group_starts(*colnames)
|
|
1243
|
+
keep = np.sort(order[starts])
|
|
1244
|
+
for colname, column in self.items():
|
|
1245
|
+
yield colname, column[keep].copy()
|
|
1246
|
+
return
|
|
1247
|
+
# For strings and objects, a Python set is faster than sorting.
|
|
1248
|
+
# We just need to avoid NaN and NaT for the 'in' checks to work.
|
|
1285
1249
|
for i, column in enumerate(columns):
|
|
1286
1250
|
if column.is_datetime():
|
|
1287
1251
|
flag = np.nanmin(column) - np.timedelta64(1, "D")
|
|
1288
1252
|
if np.isnat(flag):
|
|
1289
1253
|
flag = np.datetime64("-001-01-01")
|
|
1290
1254
|
columns[i] = column.replace_na(flag)
|
|
1255
|
+
if column.is_timedelta():
|
|
1256
|
+
unit = np.datetime_data(column.dtype)[0]
|
|
1257
|
+
flag = np.nanmin(column) - np.timedelta64(1, unit)
|
|
1258
|
+
if np.isnat(flag):
|
|
1259
|
+
flag = np.timedelta64(0, unit)
|
|
1260
|
+
columns[i] = column.replace_na(flag)
|
|
1291
1261
|
if column.is_float():
|
|
1292
1262
|
flag = np.nanmin(column) - 1
|
|
1293
1263
|
if np.isnan(flag):
|
|
1294
1264
|
flag = -1
|
|
1295
1265
|
columns[i] = column.replace_na(flag)
|
|
1296
|
-
rows = list(zip(*columns))
|
|
1297
1266
|
seen = set()
|
|
1298
1267
|
keep = []
|
|
1299
|
-
for i in
|
|
1300
|
-
if
|
|
1301
|
-
seen.add(
|
|
1268
|
+
for i, row in enumerate(zip(*columns)):
|
|
1269
|
+
if row not in seen:
|
|
1270
|
+
seen.add(row)
|
|
1302
1271
|
keep.append(i)
|
|
1303
1272
|
for colname, column in self.items():
|
|
1304
1273
|
yield colname, column[keep].copy()
|
|
@@ -51,7 +51,7 @@ def read_npz(path, *, allow_pickle=True):
|
|
|
51
51
|
return DataFrame.read_npz(path, allow_pickle=allow_pickle)
|
|
52
52
|
|
|
53
53
|
def read_parquet(path, *, columns=[], dtypes={}):
|
|
54
|
-
return DataFrame.read_parquet(path, columns=
|
|
54
|
+
return DataFrame.read_parquet(path, columns=columns, dtypes=dtypes)
|
|
55
55
|
|
|
56
56
|
read_csv.__doc__ = util.format_alias_doc(read_csv, DataFrame.read_csv)
|
|
57
57
|
read_geojson.__doc__ = util.format_alias_doc(read_geojson, GeoJSON.read)
|
|
@@ -479,7 +479,7 @@ class ListOfDicts(list):
|
|
|
479
479
|
list of whatever `function` returns.
|
|
480
480
|
|
|
481
481
|
>>> data = di.read_json("data/listings.json")
|
|
482
|
-
>>> data.map(lambda x: (x.guests, x.price))
|
|
482
|
+
>>> data.map(lambda x: (x.guests, x.price))[:3]
|
|
483
483
|
"""
|
|
484
484
|
new = list(map(function, self))
|
|
485
485
|
coerce = all(isinstance(x, dict) for x in new)
|
|
@@ -28,12 +28,16 @@ from dataiter import util
|
|
|
28
28
|
from dataiter import Vector
|
|
29
29
|
from numpy.dtypes import StringDType
|
|
30
30
|
|
|
31
|
-
def
|
|
31
|
+
def _apply(function, string, dtype=object, na_value=None):
|
|
32
|
+
if util.is_scalar(string):
|
|
33
|
+
return function(string)
|
|
32
34
|
assert isinstance(string, np.ndarray)
|
|
33
35
|
assert isinstance(string.dtype, StringDType)
|
|
34
|
-
out = np.full_like(string,
|
|
36
|
+
out = np.full_like(string, na_value, dtype)
|
|
35
37
|
na = string == dtypes.string.na_object
|
|
36
|
-
|
|
38
|
+
for i in np.flatnonzero(~na):
|
|
39
|
+
out[i] = function(string[i])
|
|
40
|
+
return Vector.fast(out, dtype)
|
|
37
41
|
|
|
38
42
|
def findall(pattern, string, flags=0):
|
|
39
43
|
"""
|
|
@@ -44,12 +48,8 @@ def findall(pattern, string, flags=0):
|
|
|
44
48
|
>>> x = di.Vector(["asdf", "1234"])
|
|
45
49
|
>>> regex.findall(r"[a-z]", x)
|
|
46
50
|
"""
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
out, na = _prep(string, object, None)
|
|
50
|
-
for i in np.flatnonzero(~na):
|
|
51
|
-
out[i] = re.findall(pattern, string[i], flags=flags)
|
|
52
|
-
return Vector.fast(out, object)
|
|
51
|
+
f = lambda x: re.findall(pattern, x, flags=flags)
|
|
52
|
+
return _apply(f, string)
|
|
53
53
|
|
|
54
54
|
def fullmatch(pattern, string, flags=0):
|
|
55
55
|
"""
|
|
@@ -60,12 +60,8 @@ def fullmatch(pattern, string, flags=0):
|
|
|
60
60
|
>>> x = di.Vector(["asdf", "1234"])
|
|
61
61
|
>>> regex.fullmatch(r"[a-z]+", x)
|
|
62
62
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
out, na = _prep(string, object, None)
|
|
66
|
-
for i in np.flatnonzero(~na):
|
|
67
|
-
out[i] = re.fullmatch(pattern, string[i], flags=flags)
|
|
68
|
-
return Vector.fast(out, object)
|
|
63
|
+
f = lambda x: re.fullmatch(pattern, x, flags=flags)
|
|
64
|
+
return _apply(f, string)
|
|
69
65
|
|
|
70
66
|
def match(pattern, string, flags=0):
|
|
71
67
|
"""
|
|
@@ -76,12 +72,8 @@ def match(pattern, string, flags=0):
|
|
|
76
72
|
>>> x = di.Vector(["asdf", "1234"])
|
|
77
73
|
>>> regex.match(r"[a-z]", x)
|
|
78
74
|
"""
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
out, na = _prep(string, object, None)
|
|
82
|
-
for i in np.flatnonzero(~na):
|
|
83
|
-
out[i] = re.match(pattern, string[i], flags=flags)
|
|
84
|
-
return Vector.fast(out, object)
|
|
75
|
+
f = lambda x: re.match(pattern, x, flags=flags)
|
|
76
|
+
return _apply(f, string)
|
|
85
77
|
|
|
86
78
|
def search(pattern, string, flags=0):
|
|
87
79
|
"""
|
|
@@ -92,12 +84,8 @@ def search(pattern, string, flags=0):
|
|
|
92
84
|
>>> x = di.Vector(["asdf", "1234"])
|
|
93
85
|
>>> regex.search(r"[a-z]", x)
|
|
94
86
|
"""
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
out, na = _prep(string, object, None)
|
|
98
|
-
for i in np.flatnonzero(~na):
|
|
99
|
-
out[i] = re.search(pattern, string[i], flags=flags)
|
|
100
|
-
return Vector.fast(out, object)
|
|
87
|
+
f = lambda x: re.search(pattern, x, flags=flags)
|
|
88
|
+
return _apply(f, string)
|
|
101
89
|
|
|
102
90
|
def split(pattern, string, maxsplit=0, flags=0):
|
|
103
91
|
"""
|
|
@@ -108,12 +96,8 @@ def split(pattern, string, maxsplit=0, flags=0):
|
|
|
108
96
|
>>> x = di.Vector(["one two three", "four"])
|
|
109
97
|
>>> regex.split(r" +", x)
|
|
110
98
|
"""
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
out, na = _prep(string, object, None)
|
|
114
|
-
for i in np.flatnonzero(~na):
|
|
115
|
-
out[i] = re.split(pattern, string[i], maxsplit=maxsplit, flags=flags)
|
|
116
|
-
return Vector.fast(out, object)
|
|
99
|
+
f = lambda x: re.split(pattern, x, maxsplit=maxsplit, flags=flags)
|
|
100
|
+
return _apply(f, string)
|
|
117
101
|
|
|
118
102
|
def sub(pattern, repl, string, count=0, flags=0):
|
|
119
103
|
"""
|
|
@@ -124,12 +108,8 @@ def sub(pattern, repl, string, count=0, flags=0):
|
|
|
124
108
|
>>> x = di.Vector(["great", "fantastic"])
|
|
125
109
|
>>> regex.sub(r"$", r"!", x)
|
|
126
110
|
"""
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
out, na = _prep(string, dtypes.string, dtypes.string.na_object)
|
|
130
|
-
for i in np.flatnonzero(~na):
|
|
131
|
-
out[i] = re.sub(pattern, repl, string[i], count=count, flags=flags)
|
|
132
|
-
return Vector.fast(out, str)
|
|
111
|
+
f = lambda x: re.sub(pattern, repl, x, count=count, flags=flags)
|
|
112
|
+
return _apply(f, string, dtypes.string, dtypes.string.na_object)
|
|
133
113
|
|
|
134
114
|
def subn(pattern, repl, string, count=0, flags=0):
|
|
135
115
|
"""
|
|
@@ -140,9 +120,5 @@ def subn(pattern, repl, string, count=0, flags=0):
|
|
|
140
120
|
>>> x = di.Vector(["great", "fantastic"])
|
|
141
121
|
>>> regex.subn(r"$", r"!", x)
|
|
142
122
|
"""
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
out, na = _prep(string, object, None)
|
|
146
|
-
for i in np.flatnonzero(~na):
|
|
147
|
-
out[i] = re.subn(pattern, repl, string[i], count=count, flags=flags)
|
|
148
|
-
return Vector.fast(out, object)
|
|
123
|
+
f = lambda x: re.subn(pattern, repl, x, count=count, flags=flags)
|
|
124
|
+
return _apply(f, string)
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
import dataiter as di
|
|
24
24
|
import numpy as np
|
|
25
25
|
import pytest
|
|
26
|
-
import tempfile
|
|
27
26
|
|
|
28
27
|
from dataiter import DataFrame
|
|
29
28
|
from dataiter import DataFrameColumn
|
|
@@ -112,12 +111,14 @@ class TestDataFrame:
|
|
|
112
111
|
data = test.data_frame("vehicles.csv")
|
|
113
112
|
assert data.make is data["make"]
|
|
114
113
|
|
|
115
|
-
def
|
|
116
|
-
data = DataFrame(a=[1, 2, 3])
|
|
117
|
-
data
|
|
118
|
-
data
|
|
119
|
-
assert
|
|
120
|
-
assert not
|
|
114
|
+
def test___dir__(self):
|
|
115
|
+
data = DataFrame(a=[1, 2, 3], items=[1, 2, 3])
|
|
116
|
+
data["b c"] = 1
|
|
117
|
+
names = dir(data)
|
|
118
|
+
assert "a" in names
|
|
119
|
+
assert "b c" not in names
|
|
120
|
+
assert names.count("items") == 1
|
|
121
|
+
assert "left_join" in names
|
|
121
122
|
|
|
122
123
|
def test___setattr__(self):
|
|
123
124
|
data = test.data_frame("vehicles.csv")
|
|
@@ -482,7 +483,7 @@ class TestDataFrame:
|
|
|
482
483
|
assert data.make.is_object()
|
|
483
484
|
assert data.model.is_object()
|
|
484
485
|
|
|
485
|
-
def test_read_csv_missing_blank(self):
|
|
486
|
+
def test_read_csv_missing_blank(self, tmp_path):
|
|
486
487
|
text = """
|
|
487
488
|
int,float,string,bool,date
|
|
488
489
|
,2.5,"test",TRUE,2025-01-01
|
|
@@ -491,14 +492,14 @@ int,float,string,bool,date
|
|
|
491
492
|
1,2.5,"test",,2025-01-01
|
|
492
493
|
1,2.5,"test",TRUE,
|
|
493
494
|
""".strip()
|
|
494
|
-
|
|
495
|
+
path = str(tmp_path / "test.csv")
|
|
495
496
|
Path(path).write_text(text, "utf-8")
|
|
496
497
|
data = DataFrame.read_csv(path)
|
|
497
498
|
for i, column in enumerate(data.columns):
|
|
498
499
|
assert column.is_na().sum() == 1
|
|
499
500
|
assert column.is_na()[i]
|
|
500
501
|
|
|
501
|
-
def test_read_csv_missing_excel(self):
|
|
502
|
+
def test_read_csv_missing_excel(self, tmp_path):
|
|
502
503
|
text = """
|
|
503
504
|
int,float,string,bool,date
|
|
504
505
|
#N/A,2.5,"test",TRUE,2025-01-01
|
|
@@ -507,14 +508,14 @@ int,float,string,bool,date
|
|
|
507
508
|
1,2.5,"test",#N/A,2025-01-01
|
|
508
509
|
1,2.5,"test",TRUE,#N/A
|
|
509
510
|
""".strip()
|
|
510
|
-
|
|
511
|
+
path = str(tmp_path / "test.csv")
|
|
511
512
|
Path(path).write_text(text, "utf-8")
|
|
512
513
|
data = DataFrame.read_csv(path)
|
|
513
514
|
for i, column in enumerate(data.columns):
|
|
514
515
|
assert column.is_na().sum() == 1
|
|
515
516
|
assert column.is_na()[i]
|
|
516
517
|
|
|
517
|
-
def test_read_csv_missing_na(self):
|
|
518
|
+
def test_read_csv_missing_na(self, tmp_path):
|
|
518
519
|
text = """
|
|
519
520
|
int,float,string,bool,date
|
|
520
521
|
NA,2.5,"test",TRUE,2025-01-01
|
|
@@ -523,14 +524,14 @@ NA,2.5,"test",TRUE,2025-01-01
|
|
|
523
524
|
1,2.5,"test",NA,2025-01-01
|
|
524
525
|
1,2.5,"test",TRUE,NA
|
|
525
526
|
""".strip()
|
|
526
|
-
|
|
527
|
+
path = str(tmp_path / "test.csv")
|
|
527
528
|
Path(path).write_text(text, "utf-8")
|
|
528
529
|
data = DataFrame.read_csv(path)
|
|
529
530
|
for i, column in enumerate(data.columns):
|
|
530
531
|
assert column.is_na().sum() == 1
|
|
531
532
|
assert column.is_na()[i]
|
|
532
533
|
|
|
533
|
-
def test_read_csv_missing_null(self):
|
|
534
|
+
def test_read_csv_missing_null(self, tmp_path):
|
|
534
535
|
text = """
|
|
535
536
|
int,float,string,bool,date
|
|
536
537
|
NULL,2.5,"test",TRUE,2025-01-01
|
|
@@ -539,7 +540,7 @@ NULL,2.5,"test",TRUE,2025-01-01
|
|
|
539
540
|
1,2.5,"test",NULL,2025-01-01
|
|
540
541
|
1,2.5,"test",TRUE,NULL
|
|
541
542
|
""".strip()
|
|
542
|
-
|
|
543
|
+
path = str(tmp_path / "test.csv")
|
|
543
544
|
Path(path).write_text(text, "utf-8")
|
|
544
545
|
data = DataFrame.read_csv(path)
|
|
545
546
|
for i, column in enumerate(data.columns):
|
|
@@ -568,37 +569,37 @@ NULL,2.5,"test",TRUE,2025-01-01
|
|
|
568
569
|
DataFrame.read_json(test.get_data_path("vehicles.json"))
|
|
569
570
|
|
|
570
571
|
@pytest.mark.filterwarnings(IGNORE_NPZ_PICKLE_WARNING)
|
|
571
|
-
def test_read_npz(self):
|
|
572
|
+
def test_read_npz(self, tmp_path):
|
|
572
573
|
orig = test.data_frame("vehicles.csv")
|
|
573
|
-
|
|
574
|
+
path = str(tmp_path / "test.npz")
|
|
574
575
|
orig.write_npz(path)
|
|
575
576
|
data = DataFrame.read_npz(path)
|
|
576
577
|
assert data == orig
|
|
577
578
|
|
|
578
579
|
@pytest.mark.filterwarnings(IGNORE_NPZ_PICKLE_WARNING)
|
|
579
|
-
def test_read_npz_path(self):
|
|
580
|
+
def test_read_npz_path(self, tmp_path):
|
|
580
581
|
orig = test.data_frame("vehicles.csv")
|
|
581
|
-
|
|
582
|
+
path = str(tmp_path / "test.npz")
|
|
582
583
|
orig.write_npz(path)
|
|
583
584
|
DataFrame.read_npz(Path(path))
|
|
584
585
|
|
|
585
|
-
def test_read_parquet(self):
|
|
586
|
+
def test_read_parquet(self, tmp_path):
|
|
586
587
|
orig = test.data_frame("vehicles.csv")
|
|
587
|
-
|
|
588
|
+
path = str(tmp_path / "test.parquet")
|
|
588
589
|
orig.write_parquet(path)
|
|
589
590
|
data = DataFrame.read_parquet(path)
|
|
590
591
|
assert data == orig
|
|
591
592
|
|
|
592
|
-
def test_read_pickle(self):
|
|
593
|
+
def test_read_pickle(self, tmp_path):
|
|
593
594
|
orig = test.data_frame("vehicles.csv")
|
|
594
|
-
|
|
595
|
+
path = str(tmp_path / "test.pkl")
|
|
595
596
|
orig.write_pickle(path)
|
|
596
597
|
data = DataFrame.read_pickle(path)
|
|
597
598
|
assert data == orig
|
|
598
599
|
|
|
599
|
-
def test_read_pickle_path(self):
|
|
600
|
+
def test_read_pickle_path(self, tmp_path):
|
|
600
601
|
orig = test.data_frame("vehicles.csv")
|
|
601
|
-
|
|
602
|
+
path = str(tmp_path / "test.pkl")
|
|
602
603
|
orig.write_pickle(path)
|
|
603
604
|
DataFrame.read_pickle(Path(path))
|
|
604
605
|
|
|
@@ -785,59 +786,59 @@ NULL,2.5,"test",TRUE,2025-01-01
|
|
|
785
786
|
assert np.all(data.make == "Talbot")
|
|
786
787
|
assert np.all(data.test == 1)
|
|
787
788
|
|
|
788
|
-
def test_write_csv(self):
|
|
789
|
+
def test_write_csv(self, tmp_path):
|
|
789
790
|
orig = test.data_frame("vehicles.csv")
|
|
790
|
-
|
|
791
|
+
path = str(tmp_path / "test.csv")
|
|
791
792
|
orig.write_csv(path)
|
|
792
793
|
data = DataFrame.read_csv(path)
|
|
793
794
|
assert data == orig
|
|
794
795
|
|
|
795
|
-
def test_write_csv_path(self):
|
|
796
|
+
def test_write_csv_path(self, tmp_path):
|
|
796
797
|
orig = test.data_frame("vehicles.csv")
|
|
797
|
-
|
|
798
|
+
path = str(tmp_path / "test.csv")
|
|
798
799
|
orig.write_csv(Path(path))
|
|
799
800
|
|
|
800
|
-
def test_write_json(self):
|
|
801
|
+
def test_write_json(self, tmp_path):
|
|
801
802
|
orig = test.data_frame("downloads.json")
|
|
802
|
-
|
|
803
|
+
path = str(tmp_path / "test.json")
|
|
803
804
|
orig.write_json(path)
|
|
804
805
|
data = DataFrame.read_json(path)
|
|
805
806
|
assert data == orig
|
|
806
807
|
|
|
807
|
-
def test_write_json_path(self):
|
|
808
|
+
def test_write_json_path(self, tmp_path):
|
|
808
809
|
orig = test.data_frame("downloads.json")
|
|
809
|
-
|
|
810
|
+
path = str(tmp_path / "test.json")
|
|
810
811
|
orig.write_json(Path(path))
|
|
811
812
|
|
|
812
813
|
@pytest.mark.filterwarnings(IGNORE_NPZ_PICKLE_WARNING)
|
|
813
|
-
def test_write_npz(self):
|
|
814
|
+
def test_write_npz(self, tmp_path):
|
|
814
815
|
orig = test.data_frame("vehicles.csv")
|
|
815
|
-
|
|
816
|
+
path = str(tmp_path / "test.npz")
|
|
816
817
|
orig.write_npz(path)
|
|
817
818
|
data = DataFrame.read_npz(path)
|
|
818
819
|
assert data == orig
|
|
819
820
|
|
|
820
821
|
@pytest.mark.filterwarnings(IGNORE_NPZ_PICKLE_WARNING)
|
|
821
|
-
def test_write_npz_path(self):
|
|
822
|
+
def test_write_npz_path(self, tmp_path):
|
|
822
823
|
orig = test.data_frame("vehicles.csv")
|
|
823
|
-
|
|
824
|
+
path = str(tmp_path / "test.npz")
|
|
824
825
|
orig.write_npz(Path(path))
|
|
825
826
|
|
|
826
|
-
def test_write_parquet(self):
|
|
827
|
+
def test_write_parquet(self, tmp_path):
|
|
827
828
|
orig = test.data_frame("vehicles.csv")
|
|
828
|
-
|
|
829
|
+
path = str(tmp_path / "test.parquet")
|
|
829
830
|
orig.write_parquet(path)
|
|
830
831
|
data = DataFrame.read_parquet(path)
|
|
831
832
|
assert data == orig
|
|
832
833
|
|
|
833
|
-
def test_write_pickle(self):
|
|
834
|
+
def test_write_pickle(self, tmp_path):
|
|
834
835
|
orig = test.data_frame("vehicles.csv")
|
|
835
|
-
|
|
836
|
+
path = str(tmp_path / "test.pkl")
|
|
836
837
|
orig.write_pickle(path)
|
|
837
838
|
data = DataFrame.read_pickle(path)
|
|
838
839
|
assert data == orig
|
|
839
840
|
|
|
840
|
-
def test_write_pickle_path(self):
|
|
841
|
+
def test_write_pickle_path(self, tmp_path):
|
|
841
842
|
orig = test.data_frame("vehicles.csv")
|
|
842
|
-
|
|
843
|
+
path = str(tmp_path / "test.pkl")
|
|
843
844
|
orig.write_pickle(Path(path))
|
|
@@ -20,8 +20,6 @@
|
|
|
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 tempfile
|
|
24
|
-
|
|
25
23
|
from dataiter import GeoJSON
|
|
26
24
|
from dataiter import test
|
|
27
25
|
from pathlib import Path
|
|
@@ -74,15 +72,15 @@ class TestGeoJSON:
|
|
|
74
72
|
assert data.head(0).to_string()
|
|
75
73
|
assert data.head(5).to_string()
|
|
76
74
|
|
|
77
|
-
def test_write(self):
|
|
75
|
+
def test_write(self, tmp_path):
|
|
78
76
|
orig = test.geojson(self.path)
|
|
79
|
-
|
|
77
|
+
path = str(tmp_path / "test.geojson")
|
|
80
78
|
orig.write(path)
|
|
81
79
|
data = GeoJSON.read(path)
|
|
82
80
|
assert data == orig
|
|
83
81
|
assert data.metadata == orig.metadata
|
|
84
82
|
|
|
85
|
-
def test_write_path(self):
|
|
83
|
+
def test_write_path(self, tmp_path):
|
|
86
84
|
orig = test.geojson(self.path)
|
|
87
|
-
|
|
85
|
+
path = str(tmp_path / "test.geojson")
|
|
88
86
|
orig.write(Path(path))
|
|
@@ -21,7 +21,6 @@
|
|
|
21
21
|
# THE SOFTWARE.
|
|
22
22
|
|
|
23
23
|
import datetime
|
|
24
|
-
import tempfile
|
|
25
24
|
|
|
26
25
|
from attd import AttributeDict
|
|
27
26
|
from dataiter import ListOfDicts
|
|
@@ -393,16 +392,16 @@ class TestListOfDicts:
|
|
|
393
392
|
assert isinstance(data[100].date, datetime.date)
|
|
394
393
|
assert isinstance(data[100].downloads, int)
|
|
395
394
|
|
|
396
|
-
def test_read_pickle(self):
|
|
395
|
+
def test_read_pickle(self, tmp_path):
|
|
397
396
|
orig = test.list_of_dicts("downloads.json")
|
|
398
|
-
|
|
397
|
+
path = str(tmp_path / "test.pkl")
|
|
399
398
|
orig.write_pickle(path)
|
|
400
399
|
data = ListOfDicts.read_pickle(path)
|
|
401
400
|
assert data == orig
|
|
402
401
|
|
|
403
|
-
def test_read_pickle_path(self):
|
|
402
|
+
def test_read_pickle_path(self, tmp_path):
|
|
404
403
|
orig = test.list_of_dicts("downloads.json")
|
|
405
|
-
|
|
404
|
+
path = str(tmp_path / "test.pkl")
|
|
406
405
|
orig.write_pickle(path)
|
|
407
406
|
ListOfDicts.read_pickle(Path(path))
|
|
408
407
|
|
|
@@ -565,38 +564,38 @@ class TestListOfDicts:
|
|
|
565
564
|
assert all("downloads" not in x for x in data)
|
|
566
565
|
assert orig._obsolete
|
|
567
566
|
|
|
568
|
-
def test_write_csv(self):
|
|
567
|
+
def test_write_csv(self, tmp_path):
|
|
569
568
|
orig = test.list_of_dicts("vehicles.csv")
|
|
570
|
-
|
|
569
|
+
path = str(tmp_path / "test.csv")
|
|
571
570
|
orig.write_csv(path)
|
|
572
571
|
data = ListOfDicts.read_csv(path)
|
|
573
572
|
assert data == orig
|
|
574
573
|
|
|
575
|
-
def test_write_csv_path(self):
|
|
574
|
+
def test_write_csv_path(self, tmp_path):
|
|
576
575
|
orig = test.list_of_dicts("vehicles.csv")
|
|
577
|
-
|
|
576
|
+
path = str(tmp_path / "test.csv")
|
|
578
577
|
orig.write_csv(Path(path))
|
|
579
578
|
|
|
580
|
-
def test_write_json(self):
|
|
579
|
+
def test_write_json(self, tmp_path):
|
|
581
580
|
orig = test.list_of_dicts("downloads.json")
|
|
582
|
-
|
|
581
|
+
path = str(tmp_path / "test.json")
|
|
583
582
|
orig.write_json(path)
|
|
584
583
|
data = ListOfDicts.read_json(path)
|
|
585
584
|
assert data == orig
|
|
586
585
|
|
|
587
|
-
def test_write_json_path(self):
|
|
586
|
+
def test_write_json_path(self, tmp_path):
|
|
588
587
|
orig = test.list_of_dicts("downloads.json")
|
|
589
|
-
|
|
588
|
+
path = str(tmp_path / "test.json")
|
|
590
589
|
orig.write_json(Path(path))
|
|
591
590
|
|
|
592
|
-
def test_write_pickle(self):
|
|
591
|
+
def test_write_pickle(self, tmp_path):
|
|
593
592
|
orig = test.list_of_dicts("downloads.json")
|
|
594
|
-
|
|
593
|
+
path = str(tmp_path / "test.pkl")
|
|
595
594
|
orig.write_pickle(path)
|
|
596
595
|
data = ListOfDicts.read_pickle(path)
|
|
597
596
|
assert data == orig
|
|
598
597
|
|
|
599
|
-
def test_write_pickle_path(self):
|
|
598
|
+
def test_write_pickle_path(self, tmp_path):
|
|
600
599
|
orig = test.list_of_dicts("downloads.json")
|
|
601
|
-
|
|
600
|
+
path = str(tmp_path / "test.pkl")
|
|
602
601
|
orig.write_pickle(Path(path))
|
|
@@ -23,7 +23,6 @@
|
|
|
23
23
|
import datetime
|
|
24
24
|
import math
|
|
25
25
|
import numpy as np
|
|
26
|
-
import tempfile
|
|
27
26
|
|
|
28
27
|
from dataiter import util
|
|
29
28
|
|
|
@@ -145,33 +144,33 @@ class TestUtil:
|
|
|
145
144
|
assert util.utruncate("abc\u200bdef", 4) == "abc\u200bd"
|
|
146
145
|
assert util.utruncate("abc\u200bdef\u200b", 4) == "abc\u200bd"
|
|
147
146
|
|
|
148
|
-
def test_xopen_bz2(self):
|
|
147
|
+
def test_xopen_bz2(self, tmp_path):
|
|
149
148
|
text = "test åäö"
|
|
150
|
-
|
|
149
|
+
path = str(tmp_path / "test.bz2")
|
|
151
150
|
with util.xopen(path, "wt") as f:
|
|
152
151
|
f.write(text)
|
|
153
152
|
with util.xopen(path, "rt") as f:
|
|
154
153
|
assert f.read() == text
|
|
155
154
|
|
|
156
|
-
def test_xopen_gz(self):
|
|
155
|
+
def test_xopen_gz(self, tmp_path):
|
|
157
156
|
text = "test åäö"
|
|
158
|
-
|
|
157
|
+
path = str(tmp_path / "test.gz")
|
|
159
158
|
with util.xopen(path, "wt") as f:
|
|
160
159
|
f.write(text)
|
|
161
160
|
with util.xopen(path, "rt") as f:
|
|
162
161
|
assert f.read() == text
|
|
163
162
|
|
|
164
|
-
def test_xopen_txt(self):
|
|
163
|
+
def test_xopen_txt(self, tmp_path):
|
|
165
164
|
text = "test åäö"
|
|
166
|
-
|
|
165
|
+
path = str(tmp_path / "test.txt")
|
|
167
166
|
with util.xopen(path, "wt") as f:
|
|
168
167
|
f.write(text)
|
|
169
168
|
with util.xopen(path, "rt") as f:
|
|
170
169
|
assert f.read() == text
|
|
171
170
|
|
|
172
|
-
def test_xopen_xz(self):
|
|
171
|
+
def test_xopen_xz(self, tmp_path):
|
|
173
172
|
text = "test åäö"
|
|
174
|
-
|
|
173
|
+
path = str(tmp_path / "test.xz")
|
|
175
174
|
with util.xopen(path, "wt") as f:
|
|
176
175
|
f.write(text)
|
|
177
176
|
with util.xopen(path, "rt") as f:
|
|
@@ -45,9 +45,9 @@ def count_digits(value):
|
|
|
45
45
|
return n, m
|
|
46
46
|
|
|
47
47
|
def format_alias_doc(alias, target):
|
|
48
|
-
return f"{target.__doc__}\n\n{' '*8}"
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
return (f"{target.__doc__}\n\n{' '*8}"
|
|
49
|
+
f".. note:: :func:`{alias.__name__}` is a convenience "
|
|
50
|
+
f"alias for :meth:`{target.__qualname__}`.")
|
|
51
51
|
|
|
52
52
|
def format_floats(seq, ksep=None):
|
|
53
53
|
precision = dataiter.PRINT_FLOAT_PRECISION
|
|
@@ -62,8 +62,7 @@ def format_floats(seq, ksep=None):
|
|
|
62
62
|
n = max(x[0] for x in digits)
|
|
63
63
|
m = max(x[1] for x in digits)
|
|
64
64
|
precision = min(m, max(0, precision - n))
|
|
65
|
-
return [f"{
|
|
66
|
-
for x in seq]
|
|
65
|
+
return [f"{x:,.{precision}f}".replace(",", ksep) for x in seq]
|
|
67
66
|
|
|
68
67
|
def generate_colnames(n):
|
|
69
68
|
return list(itertools.islice(yield_colnames(), n))
|
|
@@ -186,6 +186,10 @@ class Vector(np.ndarray):
|
|
|
186
186
|
return array.dtype.type(array)
|
|
187
187
|
return array.view(self.__class__)
|
|
188
188
|
|
|
189
|
+
def __iter__(self):
|
|
190
|
+
# NumPy iterates subclasses ~2x slower than base arrays.
|
|
191
|
+
return iter(self.view(np.ndarray))
|
|
192
|
+
|
|
189
193
|
def __repr__(self):
|
|
190
194
|
return self.to_string()
|
|
191
195
|
|
|
@@ -427,6 +431,8 @@ class Vector(np.ndarray):
|
|
|
427
431
|
return np.isnan(self)
|
|
428
432
|
if self.is_string() or self._is_string_fixed():
|
|
429
433
|
return self == dtypes.string.na_object
|
|
434
|
+
if self.is_integer() or self.is_boolean():
|
|
435
|
+
return self.fast(np.zeros(len(self), bool))
|
|
430
436
|
# Can't use np.isin here since elements can be arrays.
|
|
431
437
|
return self.fast([x is None for x in self], bool)
|
|
432
438
|
|
|
@@ -542,10 +548,13 @@ class Vector(np.ndarray):
|
|
|
542
548
|
false and 1.0 for true. Depending on how you use the data, that might
|
|
543
549
|
work as well as an object vector of ``True``, ``False`` and ``None``.
|
|
544
550
|
"""
|
|
545
|
-
if self.is_datetime():
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
551
|
+
if self.is_datetime() or self.is_timedelta():
|
|
552
|
+
unit = np.datetime_data(self.dtype)
|
|
553
|
+
# Use a coarse unit for generic dtype so that
|
|
554
|
+
# a finer unit from actual data wins in upcasting.
|
|
555
|
+
if unit[0] == "generic":
|
|
556
|
+
unit = "D"
|
|
557
|
+
return self.dtype.type("NaT", unit)
|
|
549
558
|
if self.is_float():
|
|
550
559
|
return np.nan
|
|
551
560
|
if self.is_integer():
|
|
@@ -701,7 +710,7 @@ class Vector(np.ndarray):
|
|
|
701
710
|
elif len(types) == 1 and types.copy().pop().__module__ == "numpy":
|
|
702
711
|
# If we have a regular Python list of NumPy scalars,
|
|
703
712
|
# infer type. This should be rare, but can happen.
|
|
704
|
-
dtype = types.copy().pop()
|
|
713
|
+
dtype = np.dtype(types.copy().pop())
|
|
705
714
|
na = Vector.fast([], dtype).na_value
|
|
706
715
|
else:
|
|
707
716
|
# Guess the missing value based on types in seq.
|
|
@@ -736,7 +745,7 @@ class Vector(np.ndarray):
|
|
|
736
745
|
return np.nan
|
|
737
746
|
datetimes = [datetime.date, datetime.datetime, np.datetime64]
|
|
738
747
|
if all(x in datetimes for x in types):
|
|
739
|
-
return np.datetime64("NaT")
|
|
748
|
+
return np.datetime64("NaT", "D")
|
|
740
749
|
# Usually causes dtype to be object!
|
|
741
750
|
return None
|
|
742
751
|
|
|
@@ -815,7 +824,7 @@ class Vector(np.ndarray):
|
|
|
815
824
|
strings = util.format_floats(self, ksep=ksep)
|
|
816
825
|
return self.__class__.fast(pad(strings), str)
|
|
817
826
|
if self.is_integer() and not self.is_timedelta():
|
|
818
|
-
strings = ["{:,d}".
|
|
827
|
+
strings = [f"{x:,d}".replace(",", ksep) for x in self]
|
|
819
828
|
return self.__class__.fast(pad(strings), str)
|
|
820
829
|
if self.is_object():
|
|
821
830
|
strings = [str(x) for x in self]
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["
|
|
2
|
+
requires = ["hatchling==1.32.4"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
@@ -7,10 +7,10 @@ name = "dataiter"
|
|
|
7
7
|
dynamic = ["version"]
|
|
8
8
|
description = "Simple, light-weight data frames for Python"
|
|
9
9
|
readme = "README.md"
|
|
10
|
-
license =
|
|
10
|
+
license = "MIT"
|
|
11
11
|
requires-python = ">=3.11.0"
|
|
12
12
|
authors = [{ name = "Osmo Salomaa", email = "otsaloma@iki.fi" }]
|
|
13
|
-
dependencies = ["attd>=0.3", "numpy>=2.4.0,<3.0", "pyarrow>=
|
|
13
|
+
dependencies = ["attd>=0.3", "numpy>=2.4.0,<3.0", "pyarrow>=16.0", "wcwidth>=0.1"]
|
|
14
14
|
|
|
15
15
|
[project.urls]
|
|
16
16
|
Homepage = "https://github.com/otsaloma/dataiter"
|
|
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
|