17
17
import typing
18
18
19
19
import bigframes_vendored .pandas .pandas ._typing as vendored_pandas_typing
20
+ import numpy
20
21
import pandas as pd
21
22
22
23
import bigframes .constants as constants
23
24
import bigframes .core .blocks as blocks
25
+ import bigframes .core .convert
24
26
import bigframes .core .expression as ex
25
27
import bigframes .core .indexes as indexes
26
28
import bigframes .core .scalar as scalars
@@ -44,7 +46,19 @@ def __init__(
44
46
* ,
45
47
session : typing .Optional [bigframes .session .Session ] = None ,
46
48
):
47
- block = None
49
+ import bigframes .pandas
50
+
51
+ # just ignore object dtype if provided
52
+ if dtype in {numpy .dtypes .ObjectDType , "object" }:
53
+ dtype = None
54
+
55
+ read_pandas_func = (
56
+ session .read_pandas
57
+ if (session is not None )
58
+ else (lambda x : bigframes .pandas .read_pandas (x ))
59
+ )
60
+
61
+ block : typing .Optional [blocks .Block ] = None
48
62
if copy is not None and not copy :
49
63
raise ValueError (
50
64
f"Series constructor only supports copy=True. { constants .FEEDBACK_LINK } "
@@ -55,58 +69,75 @@ def __init__(
55
69
assert index is None
56
70
block = data
57
71
58
- elif isinstance (data , SeriesMethods ):
59
- block = data ._block
72
+ # interpret these cases as both index and data
73
+ elif (
74
+ isinstance (data , SeriesMethods )
75
+ or isinstance (data , pd .Series )
76
+ or pd .api .types .is_dict_like (data )
77
+ ):
78
+ if isinstance (data , pd .Series ):
79
+ data = read_pandas_func (data )
80
+ elif pd .api .types .is_dict_like (data ):
81
+ data = read_pandas_func (pd .Series (data , dtype = dtype )) # type: ignore
82
+ dtype = None
83
+ data_block = data ._block
60
84
if index is not None :
61
85
# reindex
62
- bf_index = indexes .Index (index )
86
+ bf_index = indexes .Index (index , session = session )
63
87
idx_block = bf_index ._block
64
88
idx_cols = idx_block .value_columns
65
- block_idx , _ = idx_block .join (block , how = "left" )
66
- block = block_idx .with_index_labels (bf_index .names )
67
-
68
- elif isinstance (data , indexes .Index ):
89
+ block_idx , _ = idx_block .join (data_block , how = "left" )
90
+ data_block = block_idx .with_index_labels (bf_index .names )
91
+ block = data_block
92
+
93
+ # list-like data that will get default index
94
+ elif isinstance (data , indexes .Index ) or pd .api .types .is_list_like (data ):
95
+ data = indexes .Index (data , dtype = dtype , session = session )
96
+ dtype = (
97
+ None # set to none as it has already been applied, avoid re-cast later
98
+ )
69
99
if data .nlevels != 1 :
70
100
raise NotImplementedError ("Cannot interpret multi-index as Series." )
71
101
# Reset index to promote index columns to value columns, set default index
72
- block = data ._block .reset_index (drop = False )
102
+ data_block = data ._block .reset_index (drop = False ).with_column_labels (
103
+ data .names
104
+ )
73
105
if index is not None :
74
106
# Align by offset
75
- bf_index = indexes .Index (index )
76
- idx_block = bf_index ._block .reset_index (drop = False )
107
+ bf_index = indexes .Index (index , session = session )
108
+ idx_block = bf_index ._block .reset_index (
109
+ drop = False
110
+ ) # reset to align by offsets, and then reset back
77
111
idx_cols = idx_block .value_columns
78
- block , (l_mapping , _ ) = idx_block .join (block , how = "left" )
79
- block = block .set_index ([l_mapping [col ] for col in idx_cols ])
80
- block = block .with_index_labels (bf_index .names )
81
-
82
- if block :
83
- if name :
84
- if not isinstance (name , typing .Hashable ):
85
- raise ValueError (
86
- f"BigQuery DataFrames only supports hashable series names. { constants .FEEDBACK_LINK } "
87
- )
88
- block = block .with_column_labels ([name ])
89
- if dtype :
90
- block = block .multi_apply_unary_op (
91
- block .value_columns , ops .AsTypeOp (to_type = dtype )
92
- )
93
- else :
94
- import bigframes .pandas
112
+ data_block , (l_mapping , _ ) = idx_block .join (data_block , how = "left" )
113
+ data_block = data_block .set_index ([l_mapping [col ] for col in idx_cols ])
114
+ data_block = data_block .with_index_labels (bf_index .names )
115
+ block = data_block
95
116
96
- pd_series = pd .Series (
97
- data = data , index = index , dtype = dtype , name = name # type:ignore
98
- )
99
- pd_dataframe = pd_series .to_frame ()
100
- if pd_series .name is None :
101
- # to_frame will set default numeric column label if unnamed, but we do not support int column label, so must rename
102
- pd_dataframe = pd_dataframe .set_axis (["unnamed_col" ], axis = 1 )
103
- if session :
104
- block = session .read_pandas (pd_dataframe )._get_block ()
117
+ else : # Scalar case
118
+ if index is not None :
119
+ bf_index = indexes .Index (index , session = session )
105
120
else :
106
- # Uses default global session
107
- block = bigframes .pandas .read_pandas (pd_dataframe )._get_block ()
108
- if pd_series .name is None :
109
- block = block .with_column_labels ([None ])
121
+ bf_index = indexes .Index (
122
+ [] if (data is None ) else [0 ],
123
+ session = session ,
124
+ dtype = bigframes .dtypes .INT_DTYPE ,
125
+ )
126
+ block , _ = bf_index ._block .create_constant (data , dtype )
127
+ dtype = None
128
+ block = block .with_column_labels ([name ])
129
+
130
+ assert block is not None
131
+ if name :
132
+ if not isinstance (name , typing .Hashable ):
133
+ raise ValueError (
134
+ f"BigQuery DataFrames only supports hashable series names. { constants .FEEDBACK_LINK } "
135
+ )
136
+ block = block .with_column_labels ([name ])
137
+ if dtype :
138
+ block = block .multi_apply_unary_op (
139
+ block .value_columns , ops .AsTypeOp (to_type = dtype )
140
+ )
110
141
self ._block : blocks .Block = block
111
142
112
143
@property
@@ -145,17 +176,16 @@ def _apply_binary_op(
145
176
reverse : bool = False ,
146
177
) -> series .Series :
147
178
"""Applies a binary operator to the series and other."""
148
- if isinstance (other , pd . Series ):
149
- # TODO: Convert to BigQuery DataFrames series
150
- raise NotImplementedError (
151
- f"Pandas series not supported as operand. { constants . FEEDBACK_LINK } "
179
+ if bigframes . core . convert . is_series_convertible (other ):
180
+ self_index = indexes . Index ( self . _block )
181
+ other_series = bigframes . core . convert . to_bf_series (
182
+ other , self_index , self . _block . session
152
183
)
153
- if isinstance (other , series .Series ):
154
- (self_col , other_col , block ) = self ._align (other , how = alignment )
184
+ (self_col , other_col , block ) = self ._align (other_series , how = alignment )
155
185
156
186
name = self ._name
157
187
if (
158
- isinstance (other , series . Series )
188
+ hasattr (other , "name" )
159
189
and other .name != self ._name
160
190
and alignment == "outer"
161
191
):
@@ -166,7 +196,7 @@ def _apply_binary_op(
166
196
block , result_id = block .project_expr (expr , name )
167
197
return series .Series (block .select_column (result_id ))
168
198
169
- else :
199
+ else : # Scalar binop
170
200
name = self ._name
171
201
expr = op .as_expr (
172
202
ex .const (other ) if reverse else self ._value_column ,
0 commit comments