Я пытаюсь взять по очереди максимум (и мин) из двух столбцов, содержащих даты

from datetime import date
import pandas as pd
import numpy as np    

df = pd.DataFrame({'date_a' : [date(2015, 1, 1), date(2012, 6, 1),
                               date(2013, 1, 1), date(2016, 6, 1)],
                   'date_b' : [date(2012, 7, 1), date(2013, 1, 1), 
                               date(2014, 3, 1), date(2013, 4, 1)]})

df[['date_a', 'date_b']].max(axis=1)
Out[46]: 
0    2015-01-01
1    2013-01-01
2    2014-03-01
3    2016-06-01

Как и ожидалось. Однако если в фрейме данных содержится одно значение NaN, вся операция завершается неудачно

df_nan = pd.DataFrame({'date_a' : [date(2015, 1, 1), date(2012, 6, 1),
                                   np.NaN, date(2016, 6, 1)],
                       'date_b' : [date(2012, 7, 1), date(2013, 1, 1), 
                                   date(2014, 3, 1), date(2013, 4, 1)]})

df_nan[['date_a', 'date_b']].max(axis=1)
Out[49]: 
0   NaN 
1   NaN
2   NaN
3   NaN
dtype: float64

Что здесь происходит? Я ожидал этого результата

0    2015-01-01
1    2013-01-01
2    NaN
3    2016-06-01

Как этого достичь?

6
mortysporty 26 Авг 2017 в 23:18

3 ответа

Лучший ответ

Я бы сказал, что лучшее решение - использовать соответствующий dtype. Pandas предоставляет очень хорошо интегрированный datetime dtype. Обратите внимание, вы используете object dtypes ...

>>> df
       date_a      date_b
0  2015-01-01  2012-07-01
1  2012-06-01  2013-01-01
2         NaN  2014-03-01
3  2016-06-01  2013-04-01
>>> df.dtypes
date_a    object
date_b    object
dtype: object

Но обратите внимание, проблема исчезает при использовании

>>> df2 = df.apply(pd.to_datetime)
>>> df2
      date_a     date_b
0 2015-01-01 2012-07-01
1 2012-06-01 2013-01-01
2        NaT 2014-03-01
3 2016-06-01 2013-04-01
>>> df2.min(axis=1)
0   2012-07-01
1   2012-06-01
2   2014-03-01
3   2013-04-01
dtype: datetime64[ns]
8
juanpa.arrivillaga 26 Авг 2017 в 20:35

Похоже, это происходит, когда объекты date смешиваются с плавающей точкой (например, NaN) в столбцах. По умолчанию флаг numeric_only устанавливается из-за единственного значения с плавающей запятой. Например, замените ваш df_nan следующим:

df_float = pd.DataFrame({'date_a' : [date(2015, 1, 1), date(2012, 6, 1),
                                    1.023, date(2016, 6, 1)],
                        'date_b' : [date(2012, 7, 1), 3.14, 
                                    date(2014, 3, 1), date(2013, 4, 1)]})

print(df_float.max(1))

0   NaN
1   NaN
2   NaN
3   NaN
dtype: float64

Если для флага вручную установлено значение «ложь», это приведет к правильному выбрасыванию TypeError, потому что:

print(date(2015, 1, 1) < 1.0)

TypeError                                 Traceback (most recent call last)
<ipython-input-362-ccbf44ddb40a> in <module>()
      1 
----> 2 print(date(2015, 1, 1) < 1.0)

TypeError: unorderable types: datetime.date() < float()

Тем не менее, панды, кажется, принуждают все к NaN. Как обходной путь, преобразование в str с использованием df.astype, кажется, делает это:

out = df_nan.astype(str).max(1)
print(out) 
0    2015-01-01
1    2013-01-01
2           nan
3    2016-06-01
dtype: object

В этом случае сортировка лексикографически дает то же решение, что и раньше.

В противном случае, как Хуан предлагает вы можете привести к datetime используя pd.to_datetime:

out = df_nan.apply(pd.to_datetime, errors='coerce').max(1)
print(out)

0   2015-01-01
1   2013-01-01
2   2014-03-01
3   2016-06-01
dtype: datetime64[ns]
6
cs95 26 Авг 2017 в 21:49

Следующее должно работать:

>>> df_nan.where(df_nan.T.notnull().all()).max(axis=1)
Out[1]:
0    2015-01-01
1    2013-01-01
2          None
3    2016-06-01
dtype: object

Где:

  1. df_nan.T.notnull().all() вычисляет маску строки, не содержащей np.nan
  2. df_nan.where() применяет прежнюю маску к кадру данных
  3. .max(axis=1) получает построчный максимум

Это работает, потому что максимум массива, где все значения np.nan, равен None. Это позволяет отслеживать строки, в которых отсутствует значение, не показывая максимум.

Но это решение остается за вами, в противном случае вам нужно решение @ juanpa.arrivillaga, которое преобразует NaN в NaT.

1
FabienP 26 Авг 2017 в 21:19