Примените функцию попарно к серии pandas

У меня есть серия панд, элементы которой составляют замороженные наборы:

data = {0: frozenset({'apple', 'banana'}),
     1: frozenset({'apple', 'orange'}),
     2: frozenset({'banana'}),
     3: frozenset({'kumquat', 'orange'}),
     4: frozenset({'orange'}),
     5: frozenset({'orange', 'pear'}),
     6: frozenset({'orange', 'pear'}),
     7: frozenset({'apple', 'banana', 'pear'}),
     8: frozenset({'banana', 'persimmon'}),
     9: frozenset({'apple'}),
     10: frozenset({'banana'}),
     11: frozenset({'apple'})}

tokens = pd.Series(data); tokens

0           (apple, banana)
1           (orange, apple)
2                  (banana)
3         (orange, kumquat)
4                  (orange)
5            (orange, pear)
6            (orange, pear)
7     (apple, banana, pear)
8       (persimmon, banana)
9                   (apple)
10                 (banana)
11                  (apple)
Name: Tokens, dtype: object

Я хочу применить функцию попарно. Например, tokens.diff дает мне установленную разницу между последовательными строками:

0                   NaN
1              (orange)
2              (banana)
3     (orange, kumquat)
4                    ()
5                (pear)
6                    ()
7       (apple, banana)
8           (persimmon)
9               (apple)
10             (banana)
11              (apple)
Name: Tokens, dtype: object

Я хотел бы то же самое, но вместо разницы в наборах я хочу объединение наборов в последовательных строках. Итак, я бы в идеале хотел:

0                                 NaN
1             (orange, apple, banana)
2             (banana, orange, apply)
3           (orange, kumquat, banana)
4                   (orange, kumquat)
                                  ...

Как я могу добиться этого с помощью Pandas? Я знаю, что могу сделать это с помощью zip и компоновки списка, но надеюсь, что есть лучший способ.


person cs95    schedule 25.09.2017    source источник
comment
В последнем блоке кода вы не хотели оставить tokens.diff(), не так ли?   -  person IanS    schedule 25.09.2017
comment
@IanS Нет, спасибо, что поймал.   -  person cs95    schedule 25.09.2017
comment
Хм... это вдохновлено вопросом, заданным не так давно :)   -  person Jon Clements♦    schedule 25.09.2017
comment
@JonClements Ты понял. Я почти все решил, и это последнее препятствие.   -  person cs95    schedule 25.09.2017
comment
@Zero Думал об этом ... надеялся избежать, так как мне нужно использовать результат в операции groupby, поэтому громоздкий код означает больше проблем. Вы можете написать это в ответе, и я бы проголосовал за него, но я бы не принял его, если бы не было другого пути.   -  person cs95    schedule 25.09.2017
comment
разве вы не хотели бы также использовать izip_longest в случае значений NaN при смещении данных произвольной длины, но у меня была аналогичная идея   -  person gold_cy    schedule 25.09.2017
comment
@aws_apprentice Вы можете спокойно игнорировать NaN для этой проблемы.   -  person cs95    schedule 25.09.2017
comment
Это случай для rolling(2).apply(), но он не принимает объекты: stackoverflow.com/questions/36723003/ Я думаю, что ваш лучший выбор - зацикливание.   -  person ayhan    schedule 25.09.2017
comment
@Zero Ноль, в конце концов, я все равно использовал твой список. Вы можете создать ответ со всеми вашими решениями. Может быть, некоторые тайминги, если вы до этого. Я отмечу это.   -  person cs95    schedule 25.09.2017
comment
@JonClements Вы можете увидеть готовый продукт здесь.   -  person cs95    schedule 25.09.2017


Ответы (1)


Пара способов

Вариант 1] понимание списка

In [3631]: pd.Series([x[0].union(x[1])
                      for x in zip(tokens, tokens.shift(-1).fillna(''))],
                     index=tokens.index)
Out[3631]:
0              (orange, banana, apple)
1              (orange, apple, banana)
2            (orange, kumquat, banana)
3                    (orange, kumquat)
4                       (orange, pear)
5                       (orange, pear)
6        (orange, pear, banana, apple)
7     (persimmon, pear, banana, apple)
8           (apple, persimmon, banana)
9                      (apple, banana)
10                     (banana, apple)
11                             (apple)
dtype: object

Вариант 2] map

In [3632]: pd.Series(map(lambda x: x[0].union(x[1]), 
                         zip(tokens, tokens.shift(-1).fillna(''))),
                     index=tokens.index)
Out[3632]:
0              (orange, banana, apple)
1              (orange, apple, banana)
2            (orange, kumquat, banana)
3                    (orange, kumquat)
4                       (orange, pear)
5                       (orange, pear)
6        (orange, pear, banana, apple)
7     (persimmon, pear, banana, apple)
8           (apple, persimmon, banana)
9                      (apple, banana)
10                     (banana, apple)
11                             (apple)
dtype: object

Вариант 3] Использование concat и apply

In [3633]: pd.concat([tokens, tokens.shift(-1).fillna('')],
                     axis=1).apply(lambda x: x[0].union(x[1]), axis=1)
Out[3633]:
0              (orange, banana, apple)
1              (orange, apple, banana)
2            (orange, kumquat, banana)
3                    (orange, kumquat)
4                       (orange, pear)
5                       (orange, pear)
6        (orange, pear, banana, apple)
7     (persimmon, pear, banana, apple)
8           (apple, persimmon, banana)
9                      (apple, banana)
10                     (banana, apple)
11                             (apple)
dtype: object

Тайминги

In [3647]: tokens.shape
Out[3647]: (60000L,)

In [3648]: %timeit pd.Series([x[0].union(x[1]) for x in zip(tokens, tokens.shift(-1).fillna(''))], index=tokens.index)
10 loops, best of 3: 35 ms per loop

In [3649]: %timeit pd.Series(map(lambda x: x[0].union(x[1]), zip(tokens, tokens.shift(-1).fillna(''))), index=tokens.index)
10 loops, best of 3: 40.9 ms per loop

In [3650]: %timeit pd.concat([tokens, tokens.shift(-1).fillna('')], axis=1).apply(lambda x: x[0].union(x[1]), axis=1)
1 loop, best of 3: 2.2 s per loop

Не по теме и ради номера на diff

In [3653]: %timeit tokens.diff()
10 loops, best of 3: 10.8 ms per loop
person Zero    schedule 25.09.2017
comment
Я думал, что мне нужен сдвиг (-1), но на самом деле мне понадобился сдвиг (). stackoverflow.com/a/46402641/4909087 - person cs95; 25.09.2017
comment
Прекрасное решение, сэр. Я тоже придумал еще один вариант, но не смог превзойти понимание списка, т.е. pd.Series(tokens.apply(list) + tokens.shift(-1).fillna('').apply(list),index=tokens.index).apply(set) - person Bharath; 25.09.2017