#python #pandas #dataframe #pandas-groupby
#python #pandas #фрейм данных #pandas-groupby
Вопрос:
Еще один довольно сложный вопрос, на котором я застрял, касающийся Pandas и его функции groupby и cut. Ситуация следующая, допустим, у меня есть фрейм данных, который выглядит следующим образом:
import Pandas as pd
pd.DataFrame(data)
A B C ipv4
0 1 3 3 0.0.0.0
1 2 2 1 140.0.0.0
2 3 1 3 230.0.0.0
3 1 1 2 140.0.0.0
4 3 1 2 NaN
На этом этапе я должен добавить, что фактические фреймы данных, с которыми я здесь работаю, могут содержать миллионы (!) строк, Поэтому здесь я должен иметь в виду производительность.
Я создал функцию, которая дает мне набор полномочий A, B и C, так что pset = [(A), (B), (C), (A,B), ... ]
без пустого, вы поняли идею. Сейчас я группируюсь по каждой из этих комбинаций в цикле и создаю count_df
для каждой из них вот так:
for combination in pset:
df.groupby(list(combination))
count_df = df.size().reset_index().rename(columns={0: 'count'})
print(count_df)
A count
0 1 2
1 2 1
2 3 2
...
A B count
0 1 1 1
1 1 3 1
2 2 2 1
3 3 1 2
...
Мы приближаемся к моей проблеме: мне нужно добавить некоторую очень-очень базовую информацию о IP-классах в каждую строку count_df
с их соответствующей комбинацией A-B-C (вы можете прокрутить вниз по предоставленной ссылке до старшего бита (HOB) и посмотреть на таблицу, чтобы получить краткое представление о том, что я пытаюсь здесь сделать). df
Для этого я добавил в свой еще одну строку, содержащую первый октет каждой строки ipv4, и использовал сокращение Pandas, чтобы довольно быстро получить количество для каждого интервала:
# I use 256 as value for any row that has "NaN" instead of a real address
df["ipv4"].replace(to_replace="NaN", value="256.0.0.0", inplace=True)
df["first_octet"] = df["ipv4"].apply(lambda x: int(x.partition(".")[0]))
df["cut_group"] = pd.cut(data["first_octet"], [0, 127, 191, 223, 239, 255, 256])
print(df)
A B C ipv4 first_octet cut_group
0 1 3 3 0.0.0.0 0 (0, 127.0]
1 2 2 1 140.0.0.0 140 (127.0, 191.0]
2 3 1 3 230.0.0.0 230 (223.0, 239.0]
3 1 1 2 140.0.0.0 140 (127.0, 191.0]
4 3 1 2 256.0.0.0 256 (255.0, 256.0]
for combination in pset:
df.groupby(list(combination) ["cut_group"])
count_df = df.size().reset_index().rename(columns={0: 'count'})
print(count_df)
A cut_group count
0 1 (0, 127] 1
1 1 (127, 191] 1
2 1 (191, 223] 0
3 1 (223, 239] 0
4 1 (239, 255] 0
5 1 (255, 256] 0
6 2 (0, 127] 0
7 2 (127, 191] 1
8 2 (191, 223] 0
9 2 (223, 239] 0
10 2 (239, 255] 0
11 2 (255, 256] 0
12 3 (0, 127] 0
13 3 (127, 191] 0
14 3 (191, 223] 0
15 3 (223, 239] 1
16 3 (239, 255] 0
17 3 (255, 256] 1
...
A B cut_group count
0 1 1 (0, 127] 0
1 1 1 (127, 191] 1
2 1 1 (191, 223] 0
3 1 1 (223, 239] 0
4 1 1 (239, 255] 0
5 1 1 (255, 256] 0
6 1 2 (0, 127] 0
7 1 2 (127, 191] 0
8 1 2 (191, 223] 0
9 1 2 (223, 239] 0
10 1 2 (239, 255] 0
11 1 2 (255, 256] 0
12 1 3 (0, 127] 1
13 1 3 (127, 191] 0
14 1 3 (191, 223] 0
15 1 3 (223, 239] 0
16 1 3 (239, 255] 0
17 1 3 (255, 256] 0
18 2 1 (0, 127] 0
19 2 1 (127, 191] 0
20 2 1 (191, 223] 0
21 2 1 (223, 239] 0
22 2 1 (239, 255] 0
23 2 1 (255, 256] 0
24 2 2 (0, 127] 0
25 2 2 (127, 191] 1
26 2 2 (191, 223] 0
27 2 2 (223, 239] 0
28 2 2 (239, 255] 0
29 2 2 (255, 256] 0
30 2 3 (0, 127] 0
31 2 3 (127, 191] 0
32 2 3 (191, 223] 0
33 2 3 (223, 239] 0
34 2 3 (239, 255] 0
35 2 3 (255, 256] 0
36 3 1 (0, 127] 0
37 3 1 (127, 191] 0
38 3 1 (191, 223] 0
39 3 1 (223, 239] 1
40 3 1 (239, 255] 0
41 3 1 (255, 256] 1
42 3 2 (0, 127] 0
43 3 2 (127, 191] 0
44 3 2 (191, 223] 0
45 3 2 (223, 239] 0
46 3 2 (239, 255] 0
47 3 2 (255, 256] 0
48 3 3 (0, 127] 0
49 3 3 (127, 191] 0
50 3 3 (191, 223] 0
51 3 3 (223, 239] 0
52 3 3 (239, 255] 0
53 3 3 (255, 256] 0
...
Хорошо, итак, следующий шаг здесь для меня теперь отсутствует. Что мне нужно, так это выходные данные, которые выглядят следующим образом для каждой комбинации pset:
for combination in pset:
<???>
print(count_df)
A count (0, 127] (127, 191] (191, 223] (223, 239] (239, 255] (255, 256]
0 1 2 1 1 0 0 0 0
1 2 1 0 1 0 0 0 0
2 3 1 0 0 0 1 0 1
...
A B count (0, 127] (127, 191] (191, 223] (223, 239] (239, 255] (255, 256]
0 1 1 1 0 1 0 0 0 0
1 1 2 0 0 0 0 0 0 0
2 1 3 1 1 0 0 0 0 0
3 2 1 0 0 0 0 0 0 0
4 2 2 1 0 1 0 0 0 0
5 2 3 0 0 0 0 0 0 0
6 3 1 2 0 0 0 1 0 1
7 3 2 0 0 0 0 0 0 0
8 3 3 0 0 0 0 0 0 0
...
Я не уверен, как к этому подойти. Столбцы count_df
также могут быть A-B-C count classA classB classC classD classE classNaN
для пояснения. count
Столбец должен указывать количество того, сколько базовых строк содержало индивидуальную комбинацию A-B-C, как я бы вызвал df.groupby(list(combination)).size().reset_index().rename(columns={0: 'count'})
, столбцы интервалов должны указывать количество того, сколько базовых строк было подсчитано для отдельного класса отдельной комбинации A-B-C. Вы можете свести проблему к чему-то вроде наличия groupby с groupby1 = df.groupby(list(combination) ["cut_group"])
, а после этого другой groupby на этой, подобной groupby2 = groupby1.groupby(list(combination))
, и добавления информации о количестве классов из groupby1
, транспонированной в строки. Эти последние строки здесь являются бессмысленным кодом, просто чтобы прояснить, что я имею в виду.
Я открыт для любого предложения относительно заполнения упомянутого «пробела» в моем коде, а также для любого предложения, возможно, сделать что-то другое здесь, используя другие функции Pandas, о которых я пока не знаю. Как всегда, я рад изучить различные способы использования Pandas. Спасибо!
Ответ №1:
что вы можете сделать, так это join
a pd.get_dummies
столбца cut_group, а затем использовать sum
в groupby
, что-то вроде:
# get dummies
dummies = pd.get_dummies(df["cut_group"])
df_ = df.join(dummies) #you can reassign to df if you want
for combination in pset:
gr = df_.groupby(list(combination)) #change to df if you reassign the join to df before
count_df = (gr.size().to_frame('count')
.join(gr[dummies.columns].sum())
)
print(count_df)
Комментарии:
1. Спасибо за ваше предложение, я только что узнал о pd.get_dummies из-за этого. Вероятно, я буду использовать эту функцию позже в другом контексте!
Ответ №2:
У вашего pd.cut
есть небольшая ошибка: он игнорирует строки, первый октет которых равен 0, поскольку по умолчанию он не включает нижний край. Вместо этого запустите свои ячейки с -1.
df["first_octet"] = df["ipv4"].str.split('.', expand=True)[0].fillna(256).astype('int')
# Pivoting as CategoricalDType takes forever. I think this is a bug in pandas.
# Converting to string to make pivot_table faster
df["cut_group"] = pd.cut(df["first_octet"], [-1, 127, 191, 223, 239, 255, 256]).astype('str')
Сначала запустите сводку для всех трех столбцов (A, B, C),
tmp = pd.pivot_table(df, index=['A','B','C'], columns='cut_group', values='ipv4',
aggfunc='count', fill_value=0).rename_axis(None, axis=1)
tmp['count'] = tmp.sum(axis=1)
Затем вы можете groupby и суммировать любые комбинации из 3:
from itertools import combinations
cols = list('ABC')
pset = []
for size in [1,2,3]:
pset = [list(c) for c in combinations(cols, size)]
# pset = [['A'], ['B'], ['C'], ['A', 'B'], ['A', 'C'], ['B', 'C'], ['A', 'B', 'C']]
result = [tmp.groupby(p).sum() for p in pset]
Время выполнения зависит от изменчивости в A, B и C. Это занимает больше времени, когда есть более четкие значения. Для моего случайного набора данных (A, B, C — случайные 1-1000, 5M строк) это заняло около 40 секунд.
Комментарии:
1. Я, наконец, согласился с вашим предложением. Прежде всего, спасибо, что нашли ошибку с помощью pd.cut 🙂 работа с pd.pivot_table — потрясающая идея, я думаю, что переделаю часть своего кода, чтобы использовать эти таблицы с самого начала, так что вы помогли мне даже больше, чем вы, возможно, думаете! Это действительно повысило общую производительность всего моего проекта, отлично!