@@ -222,7 +222,6 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
222222 trace_patch = trace_spec .trace_patch .copy () or {}
223223 fit_results = None
224224 hover_header = ""
225- custom_data_len = 0
226225 for attr_name in trace_spec .attrs :
227226 attr_value = args [attr_name ]
228227 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):
243242 )
244243 ]
245244 trace_patch ["dimensions" ] = [
246- dict (label = get_label (args , name ), values = column . values )
245+ dict (label = get_label (args , name ), values = column )
247246 for (name , column ) in dims
248247 ]
249248 if trace_spec .constructor == go .Splom :
@@ -287,10 +286,8 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
287286 y = sorted_trace_data [args ["y" ]].values
288287 x = sorted_trace_data [args ["x" ]].values
289288
290- x_is_date = False
291289 if x .dtype .type == np .datetime64 :
292290 x = x .astype (int ) / 10 ** 9 # convert to unix epoch seconds
293- x_is_date = True
294291 elif x .dtype .type == np .object_ :
295292 try :
296293 x = x .astype (np .float64 )
@@ -308,21 +305,22 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
308305 "Could not convert value of 'y' into a numeric type."
309306 )
310307
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+
311313 if attr_value == "lowess" :
312314 # missing ='drop' is the default value for lowess but not for OLS (None)
313315 # we force it here in case statsmodels change their defaults
314316 trendline = sm .nonparametric .lowess (y , x , missing = "drop" )
315- trace_patch ["x" ] = trendline [:, 0 ]
316317 trace_patch ["y" ] = trendline [:, 1 ]
317318 hover_header = "<b>LOWESS trendline</b><br><br>"
318319 elif attr_value == "ols" :
319320 fit_results = sm .OLS (
320321 y , sm .add_constant (x ), missing = "drop"
321322 ).fit ()
322323 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- ]
326324 hover_header = "<b>OLS trendline</b><br>"
327325 if len (fit_results .params ) == 2 :
328326 hover_header += "%s = %g * %s + %g<br>" % (
@@ -339,8 +337,6 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
339337 hover_header += (
340338 "R<sup>2</sup>=%f<br><br>" % fit_results .rsquared
341339 )
342- if x_is_date :
343- trace_patch ["x" ] = pd .to_datetime (trace_patch ["x" ] * 10 ** 9 )
344340 mapping_labels [get_label (args , args ["x" ])] = "%{x}"
345341 mapping_labels [get_label (args , args ["y" ])] = "%{y} <b>(trend)</b>"
346342 elif attr_name .startswith ("error" ):
@@ -350,8 +346,9 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
350346 trace_patch [error_xy ] = {}
351347 trace_patch [error_xy ][arr ] = trace_data [attr_value ]
352348 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 ]
355352 elif attr_name == "hover_name" :
356353 if trace_spec .constructor not in [
357354 go .Histogram ,
@@ -368,29 +365,23 @@ def make_trace_kwargs(args, trace_spec, trace_data, mapping_labels, sizeref):
368365 go .Histogram2dContour ,
369366 ]:
370367 hover_is_dict = isinstance (attr_value , dict )
368+ customdata_cols = args .get ("custom_data" ) or []
371369 for col in attr_value :
372370 if hover_is_dict and not attr_value [col ]:
373371 continue
374372 try :
375373 position = args ["custom_data" ].index (col )
376374 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 )
390377 attr_label_col = get_decorated_label (args , col , None )
391378 mapping_labels [attr_label_col ] = "%%{customdata[%d]}" % (
392379 position
393380 )
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 ]
394385 elif attr_name == "color" :
395386 if trace_spec .constructor in [go .Choropleth , go .Choroplethmapbox ]:
396387 trace_patch ["z" ] = trace_data [attr_value ]
@@ -1029,6 +1020,16 @@ def _escape_col_name(df_input, col_name, extra):
10291020 return col_name
10301021
10311022
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+
10321033def process_args_into_dataframe (args , wide_mode , var_name , value_name ):
10331034 """
10341035 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):
11401141 length ,
11411142 )
11421143 )
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 )
11471145 elif not df_provided :
11481146 raise ValueError (
11491147 "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):
11781176 )
11791177 else :
11801178 col_name = str (argument )
1181- df_output [col_name ] = df_input [argument ]. values
1179+ df_output [col_name ] = to_unindexed_series ( df_input [argument ])
11821180 # ----------------- argument is likely a column / array / list.... -------
11831181 else :
11841182 if df_provided and hasattr (argument , "name" ):
@@ -1207,10 +1205,7 @@ def process_args_into_dataframe(args, wide_mode, var_name, value_name):
12071205 "length of previously-processed arguments %s is %d"
12081206 % (field , len (argument ), str (list (df_output .columns )), length )
12091207 )
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 )
12141209
12151210 # Finally, update argument with column name now that column exists
12161211 assert col_name is not None , (
0 commit comments