Извинения за неуместный титул;Я не мог придумать другой способ сказать это.Это проблема, с которой я столкнулся в нескольких различных формах и не могу найти удовлетворительного ответа.
Пример: скажем, я отслеживал, сколько чашек чая и кофе я выпиваю в течение недели:
In [17]: import random
...: test = pd.DataFrame({
...: 'drink' : ['tea'] * 5 + ['coffee'] * 5,
...: 'day' : ['monday', 'tuesday', 'wednesday', 'thursday', 'friday'] * 2,
...: 'cups' : [random.randrange(1, 10) for _ in range(10)]
...: })
...: test
...:
...:
Out[17]:
drink day cups
0 tea monday 1
1 tea tuesday 3
2 tea wednesday 1
3 tea thursday 7
4 tea friday 1
5 coffee monday 8
6 coffee tuesday 1
7 coffee wednesday 2
8 coffee thursday 1
9 coffee friday 1
Чтобы сравнить суммы, я хотел бы их нормализовать.Я могу легко нормализовать, разделив общее количество для каждого дня - это в значительной степени стандартный пример нормализации с пандами:
In [18]: test['day_norm'] = test.groupby('day')['cups'].transform(lambda x : x /
...: x.sum())
In [19]: test
Out[19]:
drink day cups day_norm
0 tea monday 1 0.111111
1 tea tuesday 3 0.750000
2 tea wednesday 1 0.333333
3 tea thursday 7 0.875000
4 tea friday 1 0.500000
5 coffee monday 8 0.888889
6 coffee tuesday 1 0.250000
7 coffee wednesday 2 0.666667
8 coffee thursday 1 0.125000
9 coffee friday 1 0.500000
Но, скажем, вместо этого я хочу посмотреть, как значения меняются за неделю наделение для каждой группы на значение для понедельника - т.е. я хочу, чтобы понедельник был 1, а затем через день - относительно этого.Мне удалось придумать два разных способа сделать это, оба из которых кажутся запутанными.
Первый: я могу написать функцию, которая фильтрует групповой фрейм данных, чтобы найти значение понедельника, а затем делит серию на него:
In [20]: def normalize(df):
...: monday_cups = df[df['day'] == 'monday']['cups'].mean()
...: return df['cups'] / monday_cups
...:
...: test['normalized cups'] = test.groupby('drink').apply(normalize).reset_i
...: ndex(level=0, drop=True)
...: test
...:
...:
Out[20]:
drink day cups day_norm normalized cups
0 tea monday 1 0.111111 1.000
1 tea tuesday 3 0.750000 3.000
2 tea wednesday 1 0.333333 1.000
3 tea thursday 7 0.875000 7.000
4 tea friday 1 0.500000 1.000
5 coffee monday 8 0.888889 1.000
6 coffee tuesday 1 0.250000 0.125
7 coffee wednesday 2 0.666667 0.250
8 coffee thursday 1 0.125000 0.125
9 coffee friday 1 0.500000 0.125
, но это связано с большим количеством проблем с индексом вДля того, чтобы они соответствовали исходному фрейму данных.
Два: я могу преобразовать данные в широкоформатную таблицу:
n [14]: summary = test.drop(columns=['normalized cups']).groupby(['drink', 'day'])['cups'].mean().unstack()
In [15]: summary
Out[15]:
day friday monday thursday tuesday wednesday
drink
coffee 8 7 7 8 4
tea 9 9 4 8 4
, и тогда деление станет более простым, но мне придется потратить немного времени, чтобы вернуть его коригинальный формат:
In [16]: summary.apply(lambda x : x / summary['monday']).stack().to_frame('norma
...: lized_cups').reset_index()
Out[16]:
drink day normalized_cups
0 coffee friday 1.142857
1 coffee monday 1.000000
2 coffee thursday 1.000000
3 coffee tuesday 1.142857
4 coffee wednesday 0.571429
5 tea friday 1.000000
6 tea monday 1.000000
7 tea thursday 0.444444
8 tea tuesday 0.888889
9 tea wednesday 0.444444
Есть ли более элегантный способ сделать это?У меня есть смутная идея отсортировать фрейм данных так, чтобы сначала был понедельник, а затем что-то делать с groupby
и first
, но я не могу об этом подумать!