Tengo una solicitud en la que estoy tratando de convertir un cuadro de datos de Pandas hacia y desde un objeto JSON, y estoy corriendo en un problema cuando el DF contiene un objeto Timedelta. Estoy usando Pandas 1.2.4.

Aquí está la muestra DF que he estado usando:

>>> timedelta_df = pd.DataFrame({'datetime': pd.Series(['2013-12-31T00:00:00.000Z'], dtype='datetime64[ns]'),
                                 'days': pd.Series([pd.Timedelta(days=1)])})
>>> timedelta_df
    datetime   days
0 2013-12-31 1 days
>>> timedelta_df.dtypes
datetime     datetime64[ns]
days        timedelta64[ns]
dtype: object

Luego he estado usando TO_JSON y READ_JSON para convertir el DF en JSON y volver a un DF:

>>> js_result = timedelta_df.to_json()
>>> js_result
'{"datetime":{"0":1388448000000},"days":{"0":86400000}}'
>>> result_df = pd.read_json(js_result)
>>> result_df
    datetime      days
0 2013-12-31  86400000
>>> result_df.dtypes
datetime    datetime64[ns]
days                 int64
dtype: object

Y luego, para intentar nuevamente el tipo correcto, he estado usando Astype, que parece estar donde estoy entrando en temas:

>>> result_df = result_df.astype(timedelta_df.dtypes.to_dict())
>>> result_df
    datetime                   days
0 2013-12-31 0 days 00:00:00.086400
>>> result_df.dtypes
datetime     datetime64[ns]
days        timedelta64[ns]
dtype: object

Así que estoy obteniendo el tipo correcto, pero el valor es incorrecto.

A continuación, intenté usar el formato de fecha ISO, pero obtendré un error allí en su lugar:

>>> iso_js_result = timedelta_df.to_json(date_format='iso')
>>> iso_js_result
'{"datetime":{"0":"2013-12-31T00:00:00.000Z"},"days":{"0":"P1DT0H0M0S"}}'
>>> iso_results_df = pd.read_json(iso_js_result)
>>> iso_results_df
                   datetime        days
0 2013-12-31 00:00:00+00:00  P1DT0H0M0S
>>> iso_results_df = iso_results_df.astype(timedelta_df.dtypes.to_dict())
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "F:\temp\virtualEnvironments\inference_schema_py37_dev\lib\site-packages\pandas\core\generic.py", line 5862, in astype
    col.astype(dtype=dtype[col_name], copy=copy, errors=errors)
  File "F:\temp\virtualEnvironments\inference_schema_py37_dev\lib\site-packages\pandas\core\generic.py", line 5877, in astype
    new_data = self._mgr.astype(dtype=dtype, copy=copy, errors=errors)
  File "F:\temp\virtualEnvironments\inference_schema_py37_dev\lib\site-packages\pandas\core\internals\managers.py", line 631, in astype
    return self.apply("astype", dtype=dtype, copy=copy, errors=errors)
  File "F:\temp\virtualEnvironments\inference_schema_py37_dev\lib\site-packages\pandas\core\internals\managers.py", line 427, in apply
    applied = getattr(b, f)(**kwargs)
  File "F:\temp\virtualEnvironments\inference_schema_py37_dev\lib\site-packages\pandas\core\internals\blocks.py", line 673, in astype
    values = astype_nansafe(vals1d, dtype, copy=True)
  File "F:\temp\virtualEnvironments\inference_schema_py37_dev\lib\site-packages\pandas\core\dtypes\cast.py", line 1074, in astype_nansafe
    return lib.astype_intsafe(arr.ravel(), dtype).reshape(arr.shape)
  File "pandas\_libs\lib.pyx", line 619, in pandas._libs.lib.astype_intsafe
ValueError: Could not convert object to NumPy timedelta

En este punto siento que me estoy perdiendo algo. En su mayoría, me he estado desapareciendo de los documentos de referencia de la API para TO_JSON, READ_JSON y ASTPE, y nada que lo he intentado en términos de parámetros se han resuelto para mí. También intenté usar To_Timedelta en la columna específica (no es ideal porque necesitaré averiguar dinámicamente a qué columnas se activan en la aplicación real), pero obtengo el mismo valor incorrecto allí.

Cualquier ayuda / punteros sobre lo que debería hacer aquí, si hay un enfoque adecuado, sería muy apreciado. Gracias.

2
trangevi 8 jun. 2021 a las 21:18

2 respuestas

La mejor respuesta

El problema con result_df.astype(timedelta_df.dtypes.to_dict()) que resulta en valores incorrectos es que el tipo de datos de la columna days es timedelta64[ns], es decir, espera que los nanosegundos, mientras que to_json por defecto se enderezó con Timedeltas como milisegundos.

Por lo tanto, una forma sencilla de solucionar esto, por lo tanto, será serializándolo explícitamente como nanosegundos: timedelta_df.to_json(date_unit="ns").

>>> result_df = pd.read_json(timedelta_df.to_json(date_unit="ns"))
>>> result_df.astype(timedelta_df.dtypes)
    datetime   days
0 2013-12-31 1 days

Otra forma sería decirle a pd.to_timedelta qué unidades esperar:

>>> result_df = pd.read_json(timedelta_df.to_json())
>>> pd.to_timedelta(result_df.days, unit="ms")
0   1 days
Name: days, dtype: timedelta64[ns]

O para el formato ISO:

>>> result_df = pd.read_json(timedelta_df.to_json(date_format='iso')
>>> pd.to_timedelta(result_df.days)
0   1 days
Name: days, dtype: timedelta64[ns]
1
mihi 8 jun. 2021 a las 21:13

read_json no se analiza directamente Timedelta Isoformats, por lo que days se carga como una cadena (objeto DTYPE). Tendrás que analizarlo manualmente:

iso_results_df['days'] = iso_results_df['days'].apply(pd.Timedelta)
1
RJ Adriaansen 8 jun. 2021 a las 21:16