pandas groupby.agg() принимает режим категориальных переменных, где NaN является единственной переменной для группы

#python #pandas #dataframe #group-by

Вопрос:

Я хочу найти наиболее распространенное значение для каждой группы. ОБНОВЛЕНИЕ: Если есть реальные значения и NAN, я хочу отказаться от NAN. Я хочу только НаН, когда это все ценности.

В некоторых моих группах отсутствуют все их данные. И я хотел бы, чтобы результатом в этих случаях были отсутствующие данные (NaN) как наиболее распространенное значение.

В этих случаях DataFrame.groupby.agg(pd.Series.mode) функция возвращает пустое категориальное значение. Чего я хочу, так это Нэн.

Ниже приведен пример игрушки …

 data = """
Group, Value
A,      1
A,      1
A,      1
B,      2 
C,      3   
C, 
C, 
D,
D,
"""

from io import StringIO
df = (
    pd.read_csv(StringIO(data),
                skipinitialspace=True)
    .astype('category')
)

df.groupby('Group')['Value'].agg(pd.Series.mode)
 

Что дает результат …

 A                                             1.0
B                                             2.0
C                                             3.0
D    [], Categories (3, float64): [1.0, 2.0, 3.0]
Name: Value, dtype: object
 

Мой вопрос: есть ли способ получить NAN или обнаружить пустую категорию и сделать ее NaN. ОБНОВЛЕНО: Отмечая, что я не могу использовать dropna=False , так как это дало бы мне неправильный ответ на C выше.

В контексте мой исходный фрейм данных содержит 27 миллионов строк, а мой сгруппированный фрейм-6 миллионов строк. Поэтому я хочу избежать медленных решений.

Комментарии:

1. Ты пробовал df.replace('', 'NaN').groupby('Group')['Value'].agg(pd.Series.mode) ?

2. там, где у меня есть значения, отличные от NaN, я хочу, чтобы метод mode игнорировал NaN. Таким образом, сопоставление NAN с чем-то другим в данном случае не работает.

Ответ №1:

Вы можете подать pd.Series.mode заявку, а затем pd.to_numeric с errors="coerce" :

 x = df.groupby("Group")["Value"].agg(pd.Series.mode)
print(pd.to_numeric(x, errors="coerce"))
 

С принтами:

 Group
A    1.0
B    2.0
C    3.0
D    NaN
Name: Value, dtype: float64
 

Ответ №2:

Вы можете использовать пользовательскую агрегацию и проверить, если isna().all() :

 df.groupby('Group')['Value'].agg(lambda x: x.mode() if not x.isna().all() else np.nan)

# Group
# A    1.0
# B    2.0
# C    3.0
# D    NaN
# Name: Value, dtype: float64
 

Из любопытства, приурочено к df = pd.concat([df] * 100000) (900 000 строк):

 >>> def coerce(df):
...    x = df.groupby("Group")["Value"].agg(pd.Series.mode)
...    return pd.to_numeric(x, errors="coerce")
>>> %timeit coerce(df)
22.1 ms ± 2.79 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
 
 >>> def isna(df):
...    return df.groupby('Group')['Value'].agg(lambda x: x.mode() if not x.isna().all() else np.nan)
>>> %timeit isna(df)
20.9 ms ± 732 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
 

Комментарии:

1. Спасибо за тест, очевидно isna() , что версия быстрее (для больших кадров данных). 1