@@ -222,7 +222,6 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
222
222
trace_patch = trace_spec .trace_patch .copy () or {}
223
223
fit_results = None
224
224
hover_header = ""
225
- custom_data_len = 0
226
225
for attr_name in trace_spec .attrs :
227
226
attr_value = args [attr_name ]
228
227
attr_label = get_decorated_label (args , attr_value , attr_name )
@@ -243,7 +242,7 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
243
242
)
244
243
]
245
244
trace_patch ["dimensions" ] = [
246
- dict (label = get_label (args , name ), values = column . values )
245
+ dict (label = get_label (args , name ), values = column )
247
246
for (name , column ) in dims
248
247
]
249
248
if trace_spec .constructor == go .Splom :
@@ -287,10 +286,8 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
287
286
y = sorted_trace_data [args ["y" ]].values
288
287
x = sorted_trace_data [args ["x" ]].values
289
288
290
- x_is_date = False
291
289
if x .dtype .type == np .datetime64 :
292
290
x = x .astype (int ) / 10 ** 9 # convert to unix epoch seconds
293
- x_is_date = True
294
291
elif x .dtype .type == np .object_ :
295
292
try :
296
293
x = x .astype (np .float64 )
@@ -308,21 +305,22 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
308
305
"Could not convert value of 'y' into a numeric type."
309
306
)
310
307
308
+ # preserve original values of "x" in case they're dates
309
+ trace_patch ["x" ] = sorted_trace_data [args ["x" ]][
310
+ np .logical_not (np .logical_or (np .isnan (y ), np .isnan (x )))
311
+ ]
312
+
311
313
if attr_value == "lowess" :
312
314
# missing ='drop' is the default value for lowess but not for OLS (None)
313
315
# we force it here in case statsmodels change their defaults
314
316
trendline = sm .nonparametric .lowess (y , x , missing = "drop" )
315
- trace_patch ["x" ] = trendline [:, 0 ]
316
317
trace_patch ["y" ] = trendline [:, 1 ]
317
318
hover_header = "<b>LOWESS trendline</b><br><br>"
318
319
elif attr_value == "ols" :
319
320
fit_results = sm .OLS (
320
321
y , sm .add_constant (x ), missing = "drop"
321
322
).fit ()
322
323
trace_patch ["y" ] = fit_results .predict ()
323
- trace_patch ["x" ] = x [
324
- np .logical_not (np .logical_or (np .isnan (y ), np .isnan (x )))
325
- ]
326
324
hover_header = "<b>OLS trendline</b><br>"
327
325
if len (fit_results .params ) == 2 :
328
326
hover_header += "%s = %g * %s + %g<br>" % (
@@ -339,8 +337,6 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
339
337
hover_header += (
340
338
"R<sup>2</sup>=%f<br><br>" % fit_results .rsquared
341
339
)
342
- if x_is_date :
343
- trace_patch ["x" ] = pd .to_datetime (trace_patch ["x" ] * 10 ** 9 )
344
340
mapping_labels [get_label (args , args ["x" ])] = "%{x}"
345
341
mapping_labels [get_label (args , args ["y" ])] = "%{y} <b>(trend)</b>"
346
342
elif attr_name .startswith ("error" ):
@@ -350,8 +346,9 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
350
346
trace_patch [error_xy ] = {}
351
347
trace_patch [error_xy ][arr ] = trace_data [attr_value ]
352
348
elif attr_name == "custom_data" :
353
- trace_patch ["customdata" ] = trace_data [attr_value ].values
354
- custom_data_len = len (attr_value ) # number of custom data columns
349
+ # here we store a data frame in customdata, and it's serialized
350
+ # as a list of row lists, which is what we want
351
+ trace_patch ["customdata" ] = trace_data [attr_value ]
355
352
elif attr_name == "hover_name" :
356
353
if trace_spec .constructor not in [
357
354
go .Histogram ,
@@ -368,29 +365,23 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
368
365
go .Histogram2dContour ,
369
366
]:
370
367
hover_is_dict = isinstance (attr_value , dict )
368
+ customdata_cols = args .get ("custom_data" ) or []
371
369
for col in attr_value :
372
370
if hover_is_dict and not attr_value [col ]:
373
371
continue
374
372
try :
375
373
position = args ["custom_data" ].index (col )
376
374
except (ValueError , AttributeError , KeyError ):
377
- position = custom_data_len
378
- custom_data_len += 1
379
- if "customdata" in trace_patch :
380
- trace_patch ["customdata" ] = np .hstack (
381
- (
382
- trace_patch ["customdata" ],
383
- trace_data [col ].values [:, None ],
384
- )
385
- )
386
- else :
387
- trace_patch ["customdata" ] = trace_data [col ].values [
388
- :, None
389
- ]
375
+ position = len (customdata_cols )
376
+ customdata_cols .append (col )
390
377
attr_label_col = get_decorated_label (args , col , None )
391
378
mapping_labels [attr_label_col ] = "%%{customdata[%d]}" % (
392
379
position
393
380
)
381
+
382
+ # here we store a data frame in customdata, and it's serialized
383
+ # as a list of row lists, which is what we want
384
+ trace_patch ["customdata" ] = trace_data [customdata_cols ]
394
385
elif attr_name == "color" :
395
386
if trace_spec .constructor in [go .Choropleth , go .Choroplethmapbox ]:
396
387
trace_patch ["z" ] = trace_data [attr_value ]
@@ -1029,6 +1020,16 @@ def _escape_col_name(df_input, col_name, extra):
1029
1020
return col_name
1030
1021
1031
1022
1023
+ def to_unindexed_series (x ):
1024
+ """
1025
+ assuming x is list-like or even an existing pd.Series, return a new pd.Series with
1026
+ no index, without extracting the data from an existing Series via numpy, which
1027
+ seems to mangle datetime columns. Stripping the index from existing pd.Series is
1028
+ required to get things to match up right in the new DataFrame we're building
1029
+ """
1030
+ return pd .Series (x ).reset_index (drop = True )
1031
+
1032
+
1032
1033
def process_args_into_dataframe (args , wide_mode , var_name , value_name ):
1033
1034
"""
1034
1035
After this function runs, the `all_attrables` keys of `args` all contain only
@@ -1140,10 +1141,7 @@ def process_args_into_dataframe(args, wide_mode, var_name, value_name):
1140
1141
length ,
1141
1142
)
1142
1143
)
1143
- if hasattr (real_argument , "values" ):
1144
- df_output [col_name ] = real_argument .values
1145
- else :
1146
- df_output [col_name ] = np .array (real_argument )
1144
+ df_output [col_name ] = to_unindexed_series (real_argument )
1147
1145
elif not df_provided :
1148
1146
raise ValueError (
1149
1147
"String or int arguments are only possible when a "
@@ -1178,7 +1176,7 @@ def process_args_into_dataframe(args, wide_mode, var_name, value_name):
1178
1176
)
1179
1177
else :
1180
1178
col_name = str (argument )
1181
- df_output [col_name ] = df_input [argument ]. values
1179
+ df_output [col_name ] = to_unindexed_series ( df_input [argument ])
1182
1180
# ----------------- argument is likely a column / array / list.... -------
1183
1181
else :
1184
1182
if df_provided and hasattr (argument , "name" ):
@@ -1207,10 +1205,7 @@ def process_args_into_dataframe(args, wide_mode, var_name, value_name):
1207
1205
"length of previously-processed arguments %s is %d"
1208
1206
% (field , len (argument ), str (list (df_output .columns )), length )
1209
1207
)
1210
- if hasattr (argument , "values" ):
1211
- df_output [str (col_name )] = argument .values
1212
- else :
1213
- df_output [str (col_name )] = np .array (argument )
1208
+ df_output [str (col_name )] = to_unindexed_series (argument )
1214
1209
1215
1210
# Finally, update argument with column name now that column exists
1216
1211
assert col_name is not None , (
0 commit comments