-
Notifications
You must be signed in to change notification settings - Fork 34
/
Copy pathseries.py
3776 lines (3250 loc) · 129 KB
/
series.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
from __future__ import annotations
import csv
from collections.abc import Set
from copy import deepcopy
from functools import partial
from itertools import chain
from itertools import product
import numpy as np
import typing_extensions as tp
from arraykit import array_deepcopy
from arraykit import delimited_to_arrays
from arraykit import first_true_1d
from arraykit import immutable_filter
from arraykit import mloc
from arraykit import name_filter
from arraykit import resolve_dtype
from numpy.ma import MaskedArray
from static_frame.core.assign import Assign
from static_frame.core.container import ContainerOperand
from static_frame.core.container_util import apply_binary_operator
from static_frame.core.container_util import axis_window_items
from static_frame.core.container_util import get_col_fill_value_factory
from static_frame.core.container_util import index_from_optional_constructor
from static_frame.core.container_util import index_many_concat
from static_frame.core.container_util import index_many_to_one
from static_frame.core.container_util import is_fill_value_factory_initializer
from static_frame.core.container_util import iter_component_signature_bytes
from static_frame.core.container_util import matmul
from static_frame.core.container_util import pandas_to_numpy
from static_frame.core.container_util import rehierarch_from_index_hierarchy
from static_frame.core.container_util import sort_index_for_order
from static_frame.core.display import Display
from static_frame.core.display import DisplayActive
from static_frame.core.display import DisplayHeader
from static_frame.core.display_config import DisplayConfig
from static_frame.core.display_config import DisplayFormats
from static_frame.core.doc_str import doc_inject
from static_frame.core.doc_str import doc_update
from static_frame.core.exception import AxisInvalid
from static_frame.core.exception import ErrorInitSeries
from static_frame.core.exception import ImmutableTypeError
from static_frame.core.exception import RelabelInvalid
from static_frame.core.index import Index
from static_frame.core.index_auto import IndexAutoFactory
from static_frame.core.index_auto import IndexDefaultConstructorFactory
from static_frame.core.index_auto import TIndexAutoFactory
from static_frame.core.index_auto import TIndexInitOrAuto
from static_frame.core.index_auto import TRelabelInput
from static_frame.core.index_base import IndexBase
from static_frame.core.index_correspondence import IndexCorrespondence
from static_frame.core.index_hierarchy import IndexHierarchy
from static_frame.core.node_dt import InterfaceDatetime
from static_frame.core.node_fill_value import InterfaceFillValue
from static_frame.core.node_iter import IterNodeApplyType
from static_frame.core.node_iter import IterNodeDepthLevel
from static_frame.core.node_iter import IterNodeGroup
from static_frame.core.node_iter import IterNodeGroupOther
from static_frame.core.node_iter import IterNodeNoArgMapable
from static_frame.core.node_iter import IterNodeWindow
from static_frame.core.node_re import InterfaceRe
from static_frame.core.node_selector import InterfaceAssignTrio
from static_frame.core.node_selector import InterfaceSelectTrio
from static_frame.core.node_selector import InterGetItemILocReduces
from static_frame.core.node_selector import InterGetItemLocReduces
from static_frame.core.node_str import InterfaceString
from static_frame.core.node_values import InterfaceValues
from static_frame.core.rank import RankMethod
from static_frame.core.rank import rank_1d
from static_frame.core.series_mapping import SeriesMapping
from static_frame.core.style_config import STYLE_CONFIG_DEFAULT
from static_frame.core.style_config import StyleConfig
from static_frame.core.style_config import style_config_css_factory
from static_frame.core.util import BOOL_TYPES
from static_frame.core.util import DEFAULT_SORT_KIND
from static_frame.core.util import DTYPE_NA_KINDS
from static_frame.core.util import DTYPE_OBJECT
from static_frame.core.util import EMPTY_ARRAY
from static_frame.core.util import EMPTY_SLICE
from static_frame.core.util import FILL_VALUE_DEFAULT
from static_frame.core.util import FLOAT_TYPES
from static_frame.core.util import INT_TYPES
from static_frame.core.util import NAME_DEFAULT
from static_frame.core.util import NULL_SLICE
from static_frame.core.util import STRING_TYPES
from static_frame.core.util import IterNodeType
from static_frame.core.util import ManyToOneType
from static_frame.core.util import TBoolOrBools
from static_frame.core.util import TCallableAny
from static_frame.core.util import TDepthLevel
from static_frame.core.util import TDtypeSpecifier
from static_frame.core.util import TILocSelector
from static_frame.core.util import TILocSelectorMany
from static_frame.core.util import TILocSelectorOne
from static_frame.core.util import TIndexCtorSpecifier
from static_frame.core.util import TIndexCtorSpecifiers
from static_frame.core.util import TIndexInitializer
from static_frame.core.util import TLabel
from static_frame.core.util import TLocSelector
from static_frame.core.util import TLocSelectorMany
from static_frame.core.util import TName
from static_frame.core.util import TPathSpecifierOrTextIO
from static_frame.core.util import TSeriesInitializer
from static_frame.core.util import TSortKinds
from static_frame.core.util import TUFunc
from static_frame.core.util import argmax_1d
from static_frame.core.util import argmin_1d
from static_frame.core.util import array_shift
from static_frame.core.util import array_to_duplicated
from static_frame.core.util import array_to_groups_and_locations
from static_frame.core.util import array_ufunc_axis_skipna
from static_frame.core.util import arrays_equal
from static_frame.core.util import binary_transition
from static_frame.core.util import concat_resolved
from static_frame.core.util import dtype_from_element
from static_frame.core.util import dtype_kind_to_na
from static_frame.core.util import dtype_to_fill_value
from static_frame.core.util import full_for_fill
from static_frame.core.util import iloc_to_insertion_iloc
from static_frame.core.util import intersect1d
from static_frame.core.util import is_callable_or_mapping
from static_frame.core.util import isfalsy_array
from static_frame.core.util import isin
from static_frame.core.util import isna_array
from static_frame.core.util import iterable_to_array_1d
from static_frame.core.util import slices_from_targets
from static_frame.core.util import ufunc_unique1d
from static_frame.core.util import ufunc_unique_enumerated
from static_frame.core.util import validate_dtype_specifier
from static_frame.core.util import write_optional_file
if tp.TYPE_CHECKING:
import pandas # pragma: no cover
from static_frame.core.generic_aliases import TBusAny # pylint: disable=C0412 #pragma: no cover
from static_frame.core.generic_aliases import TFrameAny # pylint: disable=C0412 #pragma: no cover
from static_frame.core.generic_aliases import TFrameGOAny # pylint: disable=C0412 #pragma: no cover
from static_frame.core.generic_aliases import TFrameHEAny # pylint: disable=C0412 #pragma: no cover
TNDArrayAny = np.ndarray[tp.Any, tp.Any] #pragma: no cover
TDtypeAny = np.dtype[tp.Any] #pragma: no cover
FrameType = tp.TypeVar('FrameType', bound=TFrameAny) #pragma: no cover
#-------------------------------------------------------------------------------
TVDtype = tp.TypeVar('TVDtype', bound=np.generic, default=tp.Any) # pylint: disable=E1123
TVIndex = tp.TypeVar('TVIndex', bound=IndexBase, default=tp.Any) # pylint: disable=E1123
def _NA_VALUES_CTOR(count: int) -> None: ...
class Series(ContainerOperand, tp.Generic[TVIndex, TVDtype]):
'''A one-dimensional, ordered, labelled container, immutable and of fixed size.
'''
__slots__ = (
'values',
'_index',
'_name',
)
values: TNDArrayAny
_index: IndexBase
_NDIM: int = 1
#---------------------------------------------------------------------------
@classmethod
def from_element(cls,
element: tp.Any,
*,
index: tp.Union[TIndexInitializer, IndexAutoFactory],
dtype: TDtypeSpecifier = None,
name: TName = None,
index_constructor: tp.Optional[TIndexCtorSpecifier] = None,
own_index: bool = False,
) -> tp.Self:
'''
Create a :obj:`static_frame.Series` from a single element. The size of the resultant container will be determined by the ``index`` argument.
Returns:
:obj:`static_frame.Series`
'''
if own_index:
index_final = index
else:
index_final = index_from_optional_constructor(index,
default_constructor=Index,
explicit_constructor=index_constructor
)
length = len(index_final) # type: ignore
dtype = None if dtype is None else np.dtype(dtype)
array = full_for_fill(
dtype,
length,
element,
resolve_fill_value_dtype=dtype is None, # True means derive from fill value
)
array.flags.writeable = False
return cls(array,
index=index_final,
name=name,
own_index=True,
)
@classmethod
def from_items(cls,
pairs: tp.Iterable[tp.Tuple[TLabel, tp.Any]],
*,
dtype: TDtypeSpecifier = None,
name: TName = None,
index_constructor: tp.Optional[tp.Callable[..., IndexBase]] = None
) -> tp.Self:
'''Series construction from an iterator or generator of pairs, where the first pair value is the index and the second is the value.
Args:
pairs: Iterable of pairs of index, value.
dtype: dtype or valid dtype specifier.
name:
index_constructor:
Returns:
:obj:`static_frame.Series`
'''
index = []
def values() -> tp.Iterator[tp.Any]:
for k, v in pairs:
# populate index list as side effect of iterating values
index.append(k)
yield v
return cls(values(),
index=index,
dtype=dtype,
name=name,
index_constructor=index_constructor,
)
@classmethod
def from_delimited(cls,
delimited: str,
*,
delimiter: str,
index: tp.Optional[TIndexInitOrAuto] = None,
dtype: TDtypeSpecifier = None,
name: TName = None,
index_constructor: tp.Optional[TIndexCtorSpecifier] = None,
skip_initial_space: bool = False,
quoting: int = csv.QUOTE_MINIMAL,
quote_char: str = '"',
quote_double: bool = True,
escape_char: tp.Optional[str] = None,
thousands_char: str = '',
decimal_char: str = '.',
own_index: bool = False,
) -> tp.Self:
'''Series construction from a delimited string.
Args:
dtype: if None, dtype will be inferred.
'''
get_col_dtype = None if dtype is None else lambda x: dtype
[array] = delimited_to_arrays(
(delimited,), # make into iterable of one string
dtypes=get_col_dtype,
delimiter=delimiter,
quoting=quoting,
quotechar=quote_char,
doublequote=quote_double,
escapechar=escape_char,
thousandschar=thousands_char,
decimalchar=decimal_char,
skipinitialspace=skip_initial_space,
)
if own_index:
index_final = index
else:
index = IndexAutoFactory(len(array)) if index is None else index
index_final = index_from_optional_constructor(index,
default_constructor=Index,
explicit_constructor=index_constructor
)
return cls(array,
index=index_final,
name=name,
own_index=True,
)
@classmethod
def from_dict(cls,
mapping: tp.Mapping[tp.Any, tp.Any],
*,
dtype: TDtypeSpecifier = None,
name: TName = None,
index_constructor: tp.Optional[tp.Callable[..., IndexBase]] = None
) -> tp.Self:
'''Series construction from a dictionary, where the first pair value is the index and the second is the value.
Args:
mapping: a dictionary or similar mapping interface.
dtype: dtype or valid dtype specifier.
Returns:
:obj:`Series`
'''
return cls.from_items(mapping.items(),
name=name,
dtype=dtype,
index_constructor=index_constructor)
@classmethod
def from_concat(cls,
containers: tp.Iterable[tp.Union[TSeriesAny, TBusAny]],
*,
index: tp.Optional[TIndexInitOrAuto] = None,
index_constructor: tp.Optional[TIndexCtorSpecifier] = None,
name: TName = NAME_DEFAULT,
) -> tp.Self:
'''
Concatenate multiple :obj:`Series` into a new :obj:`Series`.
Args:
containers: Iterable of ``Series`` from which values in the new ``Series`` are drawn.
index: If None, the resultant index will be the concatenation of all indices (assuming they are unique in combination). If ``IndexAutoFactory``, the resultant index is a auto-incremented integer index. Otherwise, the value is used as a index initializer.
index_constructor:
name:
Returns:
:obj:`static_frame.Series`
'''
array_values = []
if index is None:
indices = []
name_first = NAME_DEFAULT
name_aligned = True
for c in containers:
if name_first == NAME_DEFAULT:
name_first = c.name
elif name_first != c.name:
name_aligned = False
array_values.append(c.values)
if index is None:
indices.append(c.index)
# End quickly if empty iterable
if not array_values:
return cls((), index=index, name=name)
# returns immutable arrays
values = concat_resolved(array_values)
own_index = False
if index is None:
index = index_many_concat(indices,
cls_default=Index,
explicit_constructor=index_constructor,
)
own_index = True
elif index is IndexAutoFactory:
# set index arg to None to force IndexAutoFactory usage in creation
index = None
# else, index was supplied as an iterable, above
if name == NAME_DEFAULT:
# only derive if not explicitly set
name = name_first if name_aligned else None
return cls(values,
index=index,
name=name,
index_constructor=index_constructor,
own_index=own_index,
)
@classmethod
def from_concat_items(cls,
items: tp.Iterable[tp.Tuple[TLabel, TSeriesAny]],
*,
name: TName = None,
index_constructor: tp.Optional[TIndexCtorSpecifier] = None
) -> tp.Self:
'''
Produce a :obj:`Series` with a hierarchical index from an iterable of pairs of labels, :obj:`Series`. The :obj:`IndexHierarchy` is formed from the provided labels and the :obj:`Index` if each :obj:`Series`.
Args:
items: Iterable of pairs of label, :obj:`Series`
Returns:
:obj:`static_frame.Series`
'''
array_values = []
if index_constructor is None or isinstance(index_constructor, IndexDefaultConstructorFactory):
# default index constructor expects delivery of Indices for greater efficiency
def gen() -> tp.Iterator[tp.Tuple[TLabel, IndexBase]]:
for label, series in items:
array_values.append(series.values)
yield label, series._index
else:
def gen() -> tp.Iterator[tp.Tuple[TLabel, IndexBase]]:
for label, series in items:
array_values.append(series.values)
yield from product((label,), series._index) # pyright: ignore
values: TNDArrayAny
try:
# populates array_values as side
ih = index_from_optional_constructor(
gen(),
default_constructor=IndexHierarchy.from_index_items,
explicit_constructor=index_constructor,
)
# returns immutable array
values = concat_resolved(array_values)
own_index = True
except StopIteration:
# Default to empty when given an empty iterable
ih = None
values = EMPTY_ARRAY
own_index = False
return cls(values, index=ih, own_index=own_index, name=name)
@classmethod
def from_overlay(cls,
containers: tp.Iterable[tp.Self],
*,
index: tp.Optional[TIndexInitializer] = None,
union: bool = True,
name: TName = None,
func: tp.Callable[[TNDArrayAny], TNDArrayAny] = isna_array,
fill_value: tp.Any = FILL_VALUE_DEFAULT,
) -> tp.Self:
'''Return a new :obj:`Series` made by overlaying containers, aligned values are filled with values from subsequent containers with left-to-right precedence. Values are filled based on a passed function that must return a Boolean array. By default, that function is `isna_array`, returning True for missing values (NaN and None).
Args:
containers: Iterable of :obj:`Series`.
*
index: An :obj:`Index` or :obj:`IndexHierarchy`, or index initializer, to be used as the index upon which all containers are aligned. :obj:`IndexAutoFactory` is not supported.
union: If True, and no ``index`` argument is supplied, a union index from ``containers`` will be used; if False, the intersection index will be used.
name:
func:
fill_value:
'''
if not hasattr(containers, '__len__'):
containers = tuple(containers) # exhaust a generator
if index is None:
index = index_many_to_one(
(c.index for c in containers),
cls_default=Index,
many_to_one_type=ManyToOneType.UNION if union else ManyToOneType.INTERSECT,
)
else: # construct an index if not an index
if not isinstance(index, IndexBase):
index = Index(index)
container_iter = iter(containers)
container_first = next(container_iter)
if container_first._index.equals(index):
post = cls(container_first.values, index=index, own_index=True, name=name)
else:
# if the indices are not equal, we have to reindex, and we need to provide a fill_value that does minimal type corcion to the original
if fill_value is FILL_VALUE_DEFAULT:
fill_value = dtype_kind_to_na(container_first.dtype.kind)
post = container_first.reindex(index, fill_value=fill_value).rename(name)
for container in container_iter:
filled = post._fill_missing(container, func)
post = filled
return post
@classmethod
@doc_inject()
def from_pandas(cls,
value: 'pandas.Series',
*,
index: TIndexInitOrAuto = None,
index_constructor: TIndexCtorSpecifier = None,
name: TName = NAME_DEFAULT,
own_data: bool = False) -> tp.Self:
'''Given a Pandas Series, return a Series.
Args:
value: Pandas Series.
*
index_constructor:
name:
{own_data}
Returns:
:obj:`static_frame.Series`
'''
import pandas
if not isinstance(value, pandas.Series):
raise ErrorInitSeries(f'from_pandas must be called with a Pandas Series object, not: {type(value)}')
data = pandas_to_numpy(value, own_data=own_data)
name = name if name is not NAME_DEFAULT else value.name
own_index = False
if index is IndexAutoFactory:
index = None
elif index is not None:
pass # pass index into constructor
elif isinstance(value.index, pandas.MultiIndex):
index = IndexHierarchy.from_pandas(value.index)
own_index = True
else: # if None
index = Index.from_pandas(value.index)
own_index = index_constructor is None
return cls(data,
index=index,
index_constructor=index_constructor,
own_index=own_index,
name=name,
)
#---------------------------------------------------------------------------
def __init__(self,
values: TSeriesInitializer,
*,
index: tp.Union[TIndexInitializer, IndexAutoFactory, TIndexAutoFactory, None] = None,
name: TName = NAME_DEFAULT,
dtype: TDtypeSpecifier = None,
index_constructor: tp.Optional[TIndexCtorSpecifier] = None,
own_index: bool = False
) -> None:
'''Initializer.
Args:
values: An iterable of values to be aligned with the supplied (or automatically generated) index.
{index}
name:
dtype:
index_constructor:
{own_index}
'''
if own_index and index is None:
raise ErrorInitSeries('cannot own_index if no index is provided.')
#-----------------------------------------------------------------------
# values assignment
values_constructor = _NA_VALUES_CTOR
if not values.__class__ is np.ndarray:
if isinstance(values, dict):
raise ErrorInitSeries('use Series.from_dict to create a Series from a mapping.')
elif isinstance(values, Series):
self.values = values.values # take immutable array
if dtype is not None and dtype != values.dtype:
raise ErrorInitSeries(f'when supplying values via Series, the dtype argument is not required; if provided ({dtype}), it must agree with the dtype of the Series ({values.dtype})')
if index is None and index_constructor is None:
# set up for direct assignment below; index is always immutable
index = values.index
own_index = True
if name is NAME_DEFAULT:
name = values.name # propagate Series.name
elif hasattr(values, '__iter__') and not isinstance(values, STRING_TYPES):
# returned array is already immutable
self.values, _ = iterable_to_array_1d(values, dtype=dtype)
else: # it must be an element, or a string
raise ErrorInitSeries('Use Series.from_element to create a Series from an element.')
else: # is numpy array
if dtype is not None and dtype != values.dtype: # type: ignore
raise ErrorInitSeries(f'when supplying values via array, the dtype argument is not required; if provided ({dtype}), it must agree with the dtype of the array ({values.dtype})') # type: ignore
if values.shape == (): # type: ignore
# handle special case of NP element
def values_constructor(count: int) -> None: #pylint: disable=E0102
self.values = np.repeat(values, count) # type: ignore
self.values.flags.writeable = False
else:
self.values = immutable_filter(values) # type: ignore
self._name = None if name is NAME_DEFAULT else name_filter(name) # pyright: ignore
#-----------------------------------------------------------------------
# index assignment
self._index: IndexBase
if own_index:
self._index = index # type: ignore
elif index is None or index is IndexAutoFactory:
# if a values constructor is defined, self.values is not yet defined, and no index is supplied, the resultant shape will be of length 1. (If an index is supplied, the shape might be larger than one if an array element was given
if values_constructor is not _NA_VALUES_CTOR:
value_count = 1
else:
value_count = len(self.values)
self._index = IndexAutoFactory.from_optional_constructor(
value_count,
default_constructor=Index,
explicit_constructor=index_constructor
)
else: # an iterable of labels, or an index subclass
self._index = index_from_optional_constructor(index,
default_constructor=Index,
explicit_constructor=index_constructor
)
index_count = self._index.__len__() # pyright: ignore
if not self._index.STATIC: # pyright: ignore
raise ErrorInitSeries('non-static index cannot be assigned to Series')
if values_constructor is not _NA_VALUES_CTOR:
values_constructor(index_count) # updates self.values
# must update after calling values constructor
value_count = len(self.values)
#-----------------------------------------------------------------------
# final evaluation
if self.values.ndim != self._NDIM:
raise ErrorInitSeries('dimensionality of final values not supported')
if value_count != index_count:
raise ErrorInitSeries(
f'Index has incorrect size (got {index_count}, expected {value_count})'
)
#---------------------------------------------------------------------------
def __setstate__(self, state: tp.Any) -> None:
'''
Ensure that reanimated NP arrays are set not writeable.
'''
for key, value in state[1].items():
setattr(self, key, value)
self.values.flags.writeable = False
def __deepcopy__(self, memo: tp.Dict[int, tp.Any]) -> tp.Self:
obj = self.__class__.__new__(self.__class__)
obj.values = array_deepcopy(self.values, memo)
obj._index = deepcopy(self._index, memo)
obj._name = self._name # should be hashable/immutable
memo[id(self)] = obj
return obj
# def __copy__(self) -> tp.Self:
# '''
# Return shallow copy of this Series.
# '''
# return self.__class__(
# self._values,
# index=self._index,
# name=self._name,
# own_index=True,
# )
def _memory_label_component_pairs(self,
) -> tp.Iterable[tp.Tuple[str, tp.Any]]:
return (('Name', self._name),
('Index', self._index),
('Values', self.values)
)
# ---------------------------------------------------------------------------
def __reversed__(self) -> tp.Iterator[TLabel]:
'''
Returns a reverse iterator on the series' index.
Returns:
:obj:`Index`
'''
return reversed(self._index)
#---------------------------------------------------------------------------
# name interface
@property
@doc_inject()
def name(self) -> TName:
'''{}'''
return self._name
def rename(self,
name: TName = NAME_DEFAULT,
*,
index: TName = NAME_DEFAULT,
) -> tp.Self:
'''
Return a new Series with an updated name attribute.
'''
name = self.name if name is NAME_DEFAULT else name
i = self._index if index is NAME_DEFAULT else self._index.rename(index)
return self.__class__(self.values,
index=i,
name=name,
)
#---------------------------------------------------------------------------
# interfaces
@property
def loc(self) -> InterGetItemLocReduces[TSeriesAny, TVDtype]:
'''
Interface for label-based selection.
'''
return InterGetItemLocReduces(self._extract_loc) # type: ignore
@property
def iloc(self) -> InterGetItemILocReduces[TSeriesAny, TVDtype]:
'''
Interface for position-based selection.
'''
return InterGetItemILocReduces(self._extract_iloc)
@property
def drop(self) -> InterfaceSelectTrio[TSeriesAny]:
'''
Interface for dropping elements from :obj:`static_frame.Series`. This alway returns a `Series`.
'''
return InterfaceSelectTrio( # type: ignore
func_iloc=self._drop_iloc,
func_loc=self._drop_loc,
func_getitem=self._drop_loc
)
@property
def mask(self) -> InterfaceSelectTrio[TSeriesAny]:
'''
Interface for extracting Boolean :obj:`static_frame.Series`.
'''
return InterfaceSelectTrio( # type: ignore
func_iloc=self._extract_iloc_mask,
func_loc=self._extract_loc_mask,
func_getitem=self._extract_loc_mask
)
@property
def masked_array(self) -> InterfaceSelectTrio[TSeriesAny]:
'''
Interface for extracting NumPy Masked Arrays.
'''
return InterfaceSelectTrio( # type: ignore
func_iloc=self._extract_iloc_masked_array,
func_loc=self._extract_loc_masked_array,
func_getitem=self._extract_loc_masked_array
)
@property
def assign(self) -> InterfaceAssignTrio['SeriesAssign']:
'''
Interface for doing assignment-like selection and replacement.
'''
# NOTE: this is not a InterfaceAssignQuartet, like on Frame
return InterfaceAssignTrio( # type: ignore
func_iloc=self._extract_iloc_assign,
func_loc=self._extract_loc_assign,
func_getitem=self._extract_loc_assign,
delegate=SeriesAssign
)
#---------------------------------------------------------------------------
@property
def via_values(self) -> InterfaceValues[TSeriesAny]:
'''
Interface for applying functions to values (as arrays) in this container.
'''
return InterfaceValues(self)
@property
def via_str(self) -> InterfaceString[TSeriesAny]:
'''
Interface for applying string methods to elements in this container.
'''
def blocks_to_container(blocks: tp.Iterator[TNDArrayAny]) -> TSeriesAny:
return self.__class__(
next(blocks), # assume only one
index=self._index,
name=self._name,
own_index=True,
)
return InterfaceString(
blocks=(self.values,),
blocks_to_container=blocks_to_container,
ndim=self._NDIM,
labels=range(1)
)
@property
def via_dt(self) -> InterfaceDatetime[TSeriesAny]:
'''
Interface for applying datetime properties and methods to elements in this container.
'''
def blocks_to_container(blocks: tp.Iterator[TNDArrayAny]) -> TSeriesAny:
return self.__class__(
next(blocks), # assume only one
index=self._index,
name=self._name,
own_index=True,
)
return InterfaceDatetime(
blocks=(self.values,),
blocks_to_container=blocks_to_container,
)
def via_fill_value(self,
fill_value: object = np.nan,
) -> InterfaceFillValue[TSeriesAny]:
'''
Interface for using binary operators and methods with a pre-defined fill value.
'''
return InterfaceFillValue(
container=self,
fill_value=fill_value,
)
def via_re(self,
pattern: str,
flags: int = 0,
) -> InterfaceRe[TSeriesAny]:
'''
Interface for applying regular expressions to elements in this container.
'''
def blocks_to_container(blocks: tp.Iterator[TNDArrayAny]) -> TSeriesAny:
return self.__class__(
next(blocks), # assume only one
index=self._index,
name=self._name,
own_index=True,
)
return InterfaceRe(
blocks=(self.values,),
blocks_to_container=blocks_to_container,
pattern=pattern,
flags=flags,
)
@property
def via_mapping(self) -> SeriesMapping[tp.Any, TVDtype]:
'''
Return an object the fully implements the Python Mapping interface.
'''
# NOTE: cannot type the key from the Series as the component type is wrapped in an Index; in the case of IndexHierarchy, the key type is a object (labels are tuples)
return SeriesMapping(self) # type: ignore [arg-type]
#---------------------------------------------------------------------------
@property
def iter_group(self) -> IterNodeGroup[TSeriesAny]:
'''
Iterator of :obj:`Series`, where each :obj:`Series` matches unique values.
'''
return IterNodeGroup(
container=self,
function_items=partial(self._axis_group_items,
group_source=self.values),
function_values=partial(self._axis_group,
group_source=self.values),
yield_type=IterNodeType.VALUES,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_VALUES,
)
@property
def iter_group_items(self) -> IterNodeGroup[TSeriesAny]:
return IterNodeGroup(
container=self,
function_items=partial(self._axis_group_items,
group_source=self.values),
function_values=partial(self._axis_group,
group_source=self.values),
yield_type=IterNodeType.ITEMS,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_VALUES,
)
#---------------------------------------------------------------------------
@property
def iter_group_array(self) -> IterNodeGroup[TSeriesAny]:
'''
Iterator of :obj:`Series`, where each :obj:`Series` matches unique values.
'''
return IterNodeGroup(
container=self,
function_items=partial(self._axis_group_items,
as_array=True,
group_source=self.values),
function_values=partial(self._axis_group,
as_array=True,
group_source=self.values),
yield_type=IterNodeType.VALUES,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_VALUES,
)
@property
def iter_group_array_items(self) -> IterNodeGroup[TSeriesAny]:
return IterNodeGroup(
container=self,
function_items=partial(self._axis_group_items,
group_source=self.values,
as_array=True),
function_values=partial(self._axis_group,
group_source=self.values,
as_array=True),
yield_type=IterNodeType.ITEMS,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_VALUES,
)
#---------------------------------------------------------------------------
@property
def iter_group_labels(self) -> IterNodeDepthLevel[TSeriesAny]:
return IterNodeDepthLevel(
container=self,
function_items=self._axis_group_labels_items,
function_values=self._axis_group_labels,
yield_type=IterNodeType.VALUES,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_LABELS
)
@property
def iter_group_labels_items(self) -> IterNodeDepthLevel[TSeriesAny]:
return IterNodeDepthLevel(
container=self,
function_items=self._axis_group_labels_items,
function_values=self._axis_group_labels,
yield_type=IterNodeType.ITEMS,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_LABELS
)
#---------------------------------------------------------------------------
@property
def iter_group_labels_array(self) -> IterNodeDepthLevel[TSeriesAny]:
return IterNodeDepthLevel(
container=self,
function_items=partial(self._axis_group_labels_items,
as_array=True),
function_values=partial(self._axis_group_labels,
as_array=True),
yield_type=IterNodeType.VALUES,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_LABELS
)
@property
def iter_group_labels_array_items(self) -> IterNodeDepthLevel[TSeriesAny]:
return IterNodeDepthLevel(
container=self,
function_items=partial(self._axis_group_labels_items,
as_array=True),
function_values=partial(self._axis_group_labels,
as_array=True),
yield_type=IterNodeType.ITEMS,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_LABELS
)
#---------------------------------------------------------------------------
@property
def iter_group_other(self,
) -> IterNodeGroupOther[TSeriesAny]:
'''
Iterator of :obj:`Series`, grouped by unique values found in the passed container.
'''
return IterNodeGroupOther(
container=self,
function_items=self._axis_group_items,
function_values=self._axis_group,
yield_type=IterNodeType.VALUES,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_VALUES,
)
@property
def iter_group_other_items(self,
) -> IterNodeGroupOther[TSeriesAny]:
'''
Iterator of pairs of label, :obj:`Series`, grouped by unique values found in the passed container.
'''
return IterNodeGroupOther(
container=self,
function_items=self._axis_group_items,
function_values=self._axis_group,
yield_type=IterNodeType.ITEMS,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_VALUES,
)
#---------------------------------------------------------------------------
@property
def iter_group_other_array(self) -> IterNodeGroupOther[TSeriesAny]:
return IterNodeGroupOther(
container=self,
function_items=partial(self._axis_group_items,
as_array=True),
function_values=partial(self._axis_group,
as_array=True),
yield_type=IterNodeType.VALUES,
apply_type=IterNodeApplyType.SERIES_ITEMS_GROUP_VALUES
)
@property
def iter_group_other_array_items(self) -> IterNodeGroupOther[TSeriesAny]:
return IterNodeGroupOther(