Панды преобразовывают противоречивое поведение для списка
У меня есть образец фрагмента, который работает, как и ожидалось:
import pandas as pd
df = pd.DataFrame(data={'label': ['a', 'b', 'b', 'c'], 'wave': [1, 2, 3, 4], 'y': [0,0,0,0]})
df['new'] = df.groupby(['label'])[['wave']].transform(tuple)
Результат:
label wave y new
0 a 1 0 (1,)
1 b 2 0 (2, 3)
2 b 3 0 (2, 3)
3 c 4 0 (4,)
Это работает аналогично, если вместо tuple
в преобразовании я даю set, frozenset, dict
, но если я даю list
, я получаю совершенно неожиданный результат:
df['new'] = df.groupby(['label'])[['wave']].transform(list)
label wave y new
0 a 1 0 1
1 b 2 0 2
2 b 3 0 3
3 c 4 0 4
Существует обходной путь для получения ожидаемого результата:
df['new'] = df.groupby(['label'])[['wave']].transform(tuple)['wave'].apply(list)
label wave y new
0 a 1 0 [1]
1 b 2 0 [2, 3]
2 b 3 0 [2, 3]
3 c 4 0 [4]
Я думал об изменчивости/неизменности (list/tuple), но для set/frozenset это согласуется.
Вопрос в том, почему это работает таким образом?
Ответы
Ответ 1
Я сталкивался с подобной проблемой раньше. Основная проблема, которую я считаю, заключается в том, что когда количество элементов в списке совпадает с количеством записей в группе, оно пытается распаковать список, чтобы каждый элемент списка отображался на запись в группе.
Например, это приведет к распаковке списка, так как длина списка соответствует длине каждой группы:
df.groupby(['label'])[['wave']].transform(lambda x: list(x))
wave
0 1
1 2
2 3
3 4
Однако, если длина списка не совпадает с каждой группой, вы получите желаемое поведение:
df.groupby(['label'])[['wave']].transform(lambda x: list(x)+[0])
wave
0 [1, 0]
1 [2, 3, 0]
2 [2, 3, 0]
3 [4, 0]
Я думаю, что это побочный эффект от функции распаковки списка.
Ответ 2
Я думаю, что это ошибка в пандах. Можете ли вы открыть тикет на странице GitHub, пожалуйста?
Сначала я подумал, что это может быть потому, что list
просто неправильно обрабатывается как аргумент для .transform
, но если я это сделаю:
def create_list(obj):
print(type(obj))
return obj.to_list()
df.groupby(['label'])[['wave']].transform(create_list)
Я получаю тот же неожиданный результат. Однако если используется метод agg
, он работает напрямую:
df.groupby(['label'])['wave'].agg(list)
Out[179]:
label
a [1]
b [2, 3]
c [4]
Name: wave, dtype: object
Я не могу представить, что это намеренное поведение.
Btw. Я также нахожу подозрительным другое поведение, которое проявляется, если применить кортеж к сгруппированному ряду и сгруппированному фрейму данных. Например. если transform
применяется к серии вместо DataFrame, результатом также является не серия, содержащая списки, а серия, содержащая ints
(не забывайте о [['wave']]
, который создает одноколонный фрейм данных transform(tuple)
действительно возвращал кортежи ):
df.groupby(['label'])['wave'].transform(tuple)
Out[177]:
0 1
1 2
2 3
3 4
Name: wave, dtype: int64
Если я сделаю это снова с agg
вместо transform
, это сработает как для ['wave']
, так и для [['wave']]
Я использовал версию 0.25.0 в системе Ubuntu X86_64 для своих тестов.
Ответ 3
Поскольку DataFrames
в основном предназначен для обработки двумерных данных, в том числе массивы вместо скалярных значений могут натолкнуться на предостережение, подобное этому.
pd.DataFrame.trasnform
изначально реализован поверх .agg
:
# pandas/core/generic.py
@Appender(_shared_docs["transform"] % dict(axis="", **_shared_doc_kwargs))
def transform(self, func, *args, **kwargs):
result = self.agg(func, *args, **kwargs)
if is_scalar(result) or len(result) != len(self):
raise ValueError("transforms cannot produce " "aggregated results")
return result
Тем не менее, transform
всегда возвращает DataFrame, который должен иметь ту же длину, что и self, что по сути является входом.
Когда вы выполняете функцию .agg
на DataFrame
, она отлично работает:
df.groupby('label')['wave'].agg(list)
label
a [1]
b [2, 3]
c [4]
Name: wave, dtype: object
Проблема возникает, когда transform
пытается вернуть Series
такой же длины.
В процессе преобразования элемента groupby
, который представляет собой фрагмент из self
, и последующей его конкатенации, списки распаковываются с индексом той же длины, что и упомянутый @Allen.
Однако, когда они не выровнены, не распаковываются:
df.groupby(['label'])[['wave']].transform(lambda x: list(x) + [1])
wave
0 [1, 1]
1 [2, 3, 1]
2 [2, 3, 1]
3 [4, 1]
Обходного решения этой проблемы можно избежать transform
:
df = pd.DataFrame(data={'label': ['a', 'b', 'b', 'c'], 'wave': [1, 2, 3, 4], 'y': [0,0,0,0]})
df = df.merge(df.groupby('label')['wave'].agg(list).rename('new'), on='label')
df
label wave y new
0 a 1 0 [1]
1 b 2 0 [2, 3]
2 b 3 0 [2, 3]
3 c 4 0 [4]