Категории (3, объект): [foo < bar < baz]
Вывод «foo» находится перед «bar», и так далее. Неупорядоченные экземпляры категорий можно отсортировать с помощью as_ordered:
In [40]: my_cats_2.as_ordered()
Out[40]:
[foo, bar, baz, foo, foo, bar]
Категории (3, объект): [foo < bar < baz]
Наконец, следует отметить, что данные категорий не требуют строк, хотя я привёл только пример со строками. Массив категорий может включать в себя любой неизменяемый тип.
Использование pandas Categorical похоже на использование некодированных версий (например, массивов строк). Некоторые компоненты pandas, такие как функция groupby, лучше подходят для работы с категориями. Также есть функции, которые используют упорядоченные флаги.
Рассмотрим некоторые случайные числовые данные и функцию pandas qcut. Она возвращает pandas Categorical, который мы использовали ранее с функцией cut, но не объясняли, как работают категории:
In [41]: np.random.seed(12345)
In [42]: draws = np.random.randn(1000)
In [43]: draws[:5]
Out[43]: array([-0.2047, 0.4789, -0.5194, -0.5557, 1.9658])
Вычислим квартили данных и извлечём некоторую статистическую информацию:
In [44]: bins = pd.qcut(draws, 4)
In [45]: bins
Out[45]:
[(-0.684, -0.0101], (-0.0101, 0.63], (-0.684, -0.0101], (-0.684, -0.0101], (0.63,
3.928], ..., (-0.0101, 0.63], (-0.684, -0.0101], (-2.95, -0.684], (-0.0101, 0.63
], (0.63, 3.928]]
Length: 1000
Категории (4, интервал[float64]): [(-2.95, -0.684] < (-0.684, -0.0101] < (-0.010
1, 0.63] <
(0.63, 3.928]]
Хотя это полезно, точные значения квантилей и названия квантилей не очень полезны для создания сводных данных. Мы можем использовать параметр labels функции qcut, чтобы достичь цели:
In [46]: bins = pd.qcut(draws, 4, labels=['Q1', 'Q2', 'Q3', 'Q4'])
In [47]: bins
Out[47]:
[Q2, Q3, Q2, Q2, Q4, ..., Q3, Q2, Q1, Q3, Q4]
Длина: 1000
Категории (4, объект): [Q1 < Q2 < Q3 < Q4]
In [48]: bins.codes[:10]
Out[48]: массив([1, 2, 1, 1, 3, 3, 2, 2, 3, 3], dtype=int8)
Добавление меток к категориям не включает информацию о границах интервалов категорий, поэтому мы можем использовать groupby для извлечения некоторой сводной информации:
In [49]: bins = pd.Series(bins, name='quartile')
In [50]: results = (pd.Series(draws)
....: .groupby(bins)
....: .agg(['count', 'min', 'max'])
....: .reset_index())
In [51]: results
Out[51]:
quartile count min max
0 Q1 250 -2.949343 -0.685484
1 Q2 250 -0.683066 -0.010115
2 Q3 250 -0.010032 0.628894
3 Q4 250 0.634238 3.927528
Столбец квартилей сохраняет исходную информацию о категориях, включая порядок:
In [52]: results['quartile']
Out[52]:
0 Q1
1 Q2
2 Q3
3 Q4
Имя: quartile, dtype: category
Категории (4, объект): [Q1 < Q2 < Q3 < Q4]
Если вы выполняете большой объём анализа на определённом наборе данных, преобразование его в категории может значительно повысить эффективность. Обычно использование памяти для столбцов DataFrame с категориями намного меньше. Рассмотрим серию, содержащую миллион элементов, и несколько разных категорий:
In [53]: N = 10000000
In [54]: draws = pd.Series(np.random.randn(N))
In [55]: labels = pd.Series(['foo', 'bar', 'baz', 'qux'] * (N // 4))
Теперь преобразуем метки в категории:
In [56]: categories = labels.astype('category')
В этом случае использование памяти для меток намного больше, чем для категорий:
In [57]: labels.memory_usage()
Out[57]: 80000080
In [58]: categories.memory_usage()
Out[58]: 10000272
Преобразование в категории не является бесплатным, но это единовременная стоимость:
In [59]: %time _ = labels.astype('category')
CPU times: user 490 ms, sys: 240 ms, total: 730 ms
Wall time: 726 ms
GroupBy работает гораздо быстрее с категориями, потому что базовый алгоритм использует массив целых чисел, а не массив строк.
Серия, содержащая данные категорий, имеет несколько специальных методов, похожих на методы Series.str для строк. Он также предоставляет удобный способ использования и управления категориями. Посмотрите на эту серию:
In [60]: s = pd.Series(['a', 'b', 'c', 'd'] * 2)
In [61]: cat_s = s.astype('category')
In [61]: cat_s
Out[61]:
0 a
1 b
2 c
3 d
4 a
5 b
6 c
7 d
dtype: category
Категории (4, объект): ['a', 'b', 'c', 'd']
Специальный атрибут cat предоставляет доступ к методам категорий:
In [63]: cat_s.cat.codes
Out[63]:
0 0
1 1
2 2
3 3
4 0
5 1
6 2
7 3
dtype: int8
In [64]: cat_s.cat.categories
Out[64]: Index(['a', 'b', 'c', 'd'], dtype='object')
Предположим, мы знаем фактический набор категорий данных, который выходит за рамки четырёх значений в данных. Мы можем изменить их с помощью метода set_categories:
In [65]: actual_categories = ['a', 'b', 'c', 'd', 'e']
In [66]: cat_s2 = cat_s.cat.set_categories(actual_categories)
In [67]: cat_s2
Out[67]:
0 a
1 b
2 c
3 d
4 a
5 b
6 c
7 d
dtype: category
Категории (5, объект): ['a', 'b', 'c', 'd', 'e']
Несмотря на то, что данные выглядят одинаково, новые категории будут отражены в их операциях. Например, если они есть, value_counts будет отражать категории:
In [68]: cat_s.value_counts()
Out[68]:
d 2
c 2
b 2
a 2
dtype: int64
In [69]: cat_s2.value_counts()
Out[69]:
d 2
c 2
b 2
a 2
e 0
dtype: int64
Для больших наборов данных категории часто используются как удобный инструмент для экономии памяти и повышения производительности. После фильтрации большого DataFrame или серии многие категории могут не появиться в данных. Мы можем удалить неиспользуемые категории с помощью метода remove_unused_categories:
In [70]: cat_s3 = cat_s[cat_s.isin(['a', 'b'])]
In [71]: cat_s3
``` ```
0 a
1 b
4 a
5 b
dtype: category
Категории (4, объект): [a, b, c, d]
В таблице 12-1 перечислены доступные методы классификации.
Методы классификации pandas.Series |
In [73]: cat_s = pd.Series(['a', 'b', 'c', 'd'] * 2, dtype='category')
Как упоминалось в предыдущей главе, функция pandas.get_dummies может преобразовать эти классифицированные данные в DataFrame, содержащий фиктивные переменные:
In [74]: pd.get_dummies(cat_s)
Out[74]:
a b c d
0 1 0 0 0
1 0 1 0 0
2 0 0 1 0
3 0 0 0 1
4 1 0 0 0
5 0 1 0 0
6 0 0 1 0
7 0 0 0 1
Хотя мы подробно изучили методы Series и DataFrame GroupBy в главе 10, есть ещё несколько полезных методов.
В главе 10 мы изучили метод apply для группового преобразования. Существует ещё один метод transform, который похож на apply, но имеет некоторые ограничения на используемую функцию:
Рассмотрим простой пример:
In [75]: df = pd.DataFrame({'key': ['a', 'b', 'c'] * 4,
....: 'value': np.arange(12.)})
In [76]: df
Out[76]:
key value
0 a 0.0
1 b 1.0
2 c 2.0
3 a 3.0
4 b 4.0
5 c 5.0
6 a 6.0
7 b 7.0
8 c 8.0
9 a 9.0
10 b 10.0
11 c 11.0
Группируем по ключу:
In [77]: g = df.groupby('key').value
In [78]: g.mean()
Out[78]:
key
a 4.5
b 5.5
c 6.5
Name: value, dtype: float64
Предположим, мы хотим создать Series того же размера, что и df['value'], но со значениями, заменёнными средними значениями по группам. Мы можем передать функцию lambda x: x.mean() для преобразования:
In [79]: g.transform(lambda x: x.mean())
Out[79]:
0 4.5
1 5.5
2 6.5
3 4.5
4 5.5
5 6.5
6 4.5
7 5.5
8 6.5
9 4.5
10 5.5
11 6.5
Name: value, dtype: float64
Для встроенных функций агрегации мы можем передать строковое псевдоним в качестве метода agg GroupBy:
In [80]: g.transform('mean')
Out[80]:
0 4.5
1 5.5
2 6.5
3 4.5
4 5.5
5 6.5
6 4.5
7 5.5
8 6.5
9 4.5
10 5.5
11 6.5
Name: value, dtype: float64
Подобно apply, метод transform возвращает Series, но результат должен быть того же размера. Например, мы можем умножить каждую группу на 2:
In [81]: g.transform(lambda x: x * 2)
Out[81]:
0 0.0
1 2.0
2 4.0
3 6.0
4 8.0
5 10.0
6 12.0
7 14.0
8 16.0
9 18.0
10 20.0
11 22.0
Name: value, dtype: float64
Давайте рассмотрим более сложный пример, где мы вычисляем ранг каждой группы в порядке убывания:
In [82]: g.transform(lambda x: x.rank(ascending=False))
Out[82]:
0 4.0
1 4.0
2 4.0
3 3.0
4 3.0
5 3.0
6 2.0
7 2.0
8 2.0
9 1.0
10 1.0
11 1.0
Name: value, dtype: float64
Вот пример функции группового преобразования, созданной из простой функции агрегации:
def normalize(x):
return (x - x.mean()) / x.std()
Мы можем получить эквивалентные результаты с помощью transform или apply:
In [84]: g.transform(normalize)
Out[84]:
0 -1.161895
1 -1.161895
2 -1.161895
3 -0.387298
4 -0.387298
5 -0.387298
6 0.387298
7 0.387298
8 0.387298
9 1.161895
10 1.161895
11 1.161895
Name: value, dtype: float64
In [85]: g.apply(normalize)
Out[85]:
0 -1.161895
1 -1.161895
2 -1.161895
3 -0.387298
4 -0.387298
5 -0.387298
6 0.387298
7 0.387298
8 0.387298
9 1.161895
10 1.161895
11 1.161895
Name: value, dtype: float64
Встроенная функция агрегации, такая как mean или sum, обычно быстрее, чем функция apply, и быстрее, чем transform. Это позволяет нам выполнять так называемую операцию «распаковки» (unwrapped) группировки:
In [86]: g.transform('mean')
Out[86]:
0 4.5
1 5.5
2 6.5
3 4.5
4 5.5
5 6.5
6 4.5
7 5.5
8 6.5
9 4.5
10 5.5
11 6.5
Name: value, dtype: float64
In [87]: normalized = (df['value'] - g.transform('mean')) / g.transform('std')
In [88]: normalized
Out[88]:
0 -1.161895
1 -1.161895
2 -1.161895
3 -0.387298
4 -0.387298
5 -0.387298
6 0.387298
7 0.387298
8 0.387298
9 1.161895
10 1.161895
11 1.161895
Name: value, dtype: float64
Операция «распаковки» группировки может включать в себя несколько групповых агрегаций, но векторизованные операции всё равно принесут пользу. Перевод текста:
Здесь мы можем использовать «time» в качестве индекса, а затем выполнить передискретизацию:
In [93]: df.set_index('time').resample('5min').count()
Out[93]:
value
time
2017-05-20 00:00:00 5
2017-05-20 00:05:00 5
2017-05-20 00:10:00 5
Предположим, что DataFrame содержит несколько временных рядов, и мы используем дополнительный столбец с ключом для их маркировки:
In [94]: df2 = pd.DataFrame({'time': times.repeat(3),
....: 'key': np.tile(['a', 'b', 'c'], N),
....: 'value': np.arange(N * 3.)})
In [95]: df2[:7]
Out[95]:
key time value
0 a 2017-05-20 00:00:00 0.0
1 b 2017-05-20 00:00:00 1.0
2 c 2017-05-20 00:00:00 2.0
3 a 2017-05-20 00:01:00 3.0
4 b 2017-05-20 00:01:00 4.0
5 c 2017-05-20 00:01:00 5.0
6 a 2017-05-20 00:02:00 6.0
Чтобы выполнить повторную выборку для каждого значения ключа, мы вводим объект pandas.TimeGrouper:
In [96]: time_key = pd.TimeGrouper('5min')
Затем мы устанавливаем индекс времени, группируем по ключу и time_key, и агрегируем данные:
In [97]: resampled = (df2.set_index('time')
....: .groupby(['key', time_key])
....: .sum())
In [98]: resampled
Out[98]:
value
key time
a 2017-05-20 00:00:00 30.0
2017-05-20 00:05:00 105.0
2017-05-20 00:10:00 180.0
b 2017-05-20 00:00:00 35.0
2017-05-20 00:05:00 110.0
2017-05-20 00:10:00 185.0
c 2017-05-20 00:00:00 40.0
2017-05-20 00:05:00 115.0
2017-05-20 00:10:00 190.0
In [99]: resampled.reset_index()
Out[99]:
key time value
0 a 2017-05-20 00:00:00 30.0
1 a 2017-05-20 00:05:00 105.0
2 a 2017-05-20 00:10:00 180.0
3 b 2017-05-20 00:00:00 35.0
4 b 2017-05-20 00:05:00 110.0
5 b 2017-05-20 00:10:00 185.0
6 c 2017-05-20 00:00:00 40.0
7 c 2017-05-20 00:05:00 115.0
8 c 2017-05-20 00:10:00 190.0
Ограничение использования TimeGrouper заключается в том, что время должно быть индексом Series или DataFrame.
При выполнении ряда преобразований над набором данных вы можете обнаружить, что созданные временные переменные фактически не используются в анализе. Рассмотрим следующий пример:
df = load_data()
df2 = df[df['col2'] < 0]
df2['col1_demeaned'] = df2['col1'] - df2['col1'].mean()
result = df2.groupby('key').col1_demeaned.std()
Хотя здесь не используются реальные данные, этот пример показывает некоторые новые методы. Во-первых, метод DataFrame.assign представляет собой функцию, подобную df[k] = v. Он не изменяет объект на месте, а возвращает новый изменённый DataFrame. Таким образом, следующие утверждения эквивалентны:
# Обычный нефункциональный способ
df2 = df.copy()
df2['k'] = v
# Функциональный способ присвоения
df2 = df.assign(k=v)
Возможно, присвоение на месте будет быстрее, чем assign, но assign позволяет легко выполнять программирование цепочек:
result = (df2.assign(col1_demeaned=df2.col1 - df2.col2.mean())
.groupby('key')
.col1_demeaned.std())
Я использовал внешние скобки, чтобы упростить добавление разрывов строк.
При использовании программирования цепочками следует учитывать, что вам может потребоваться работать с временными объектами. В предыдущем примере мы не можем использовать результат load_data до тех пор, пока он не будет присвоен временной переменной df. Чтобы сделать это, функции assign и многие другие функции pandas могут принимать аргументы, подобные функциям, которые можно вызывать (callable). Чтобы продемонстрировать вызываемые объекты, рассмотрим фрагмент предыдущего примера:
df = load_data()
df2 = df[lambda x: x['col2'] < 0]
Его можно переписать следующим образом:
df = (load_data()
[lambda x: x.col2 < 0])
Здесь результат load_data не присваивается какой-либо переменной, поэтому функция, переданная в [ ], привязывается к объекту на этом шаге.
Мы можем записать весь процесс в виде одной цепочки выражений:
result = (load_data()
[lambda x: x.col2 < 0]
.assign(col1_demeaned=lambda x: x.col1 - x.col1.mean())
.groupby('key')
.col1_demeaned.std())
Решение о том, писать ли код таким образом, является вопросом привычки, и разделение его на несколько шагов может улучшить читаемость.
Вы можете использовать встроенные функции и методы Python для выполнения многих задач с помощью pandas функций и методов, принимающих Series или возвращающих DataFrame. Однако иногда вам нужно использовать свои собственные функции или функции из сторонних библиотек. Здесь на помощь приходят методы конвейера.
Рассмотрим следующий вызов функции:
a = f(df, arg1=v1)
b = g(a, v2, arg3=v3)
c = h(b, arg4=v4)
Когда вы используете функции, возвращающие Series или DataFrame, вы можете вызвать pipe для их перезаписи:
result = (df.pipe(f, arg1=v1)
.pipe(g, v2, arg3=v3)
.pipe(h, arg4=v4))
Функции f(df) и df.pipe(f) эквивалентны, но pipe делает цепочку более удобной.
Другое полезное применение pipe — это абстрагирование операций до многоразовых функций. Рассмотрим пример функции, которая вычитает среднее значение группы из столбца:
g = df.groupby(['key1', 'key2'])
df['col1'] = df['col1'] - g.transform('mean')
Допустим, вы хотите преобразовать несколько столбцов и изменить ключи группировки. Кроме того, вы хотите использовать программирование цепочкой для этого преобразования. Вот один из способов:
def group_demean(df, by, cols):
``` ```
result = df.copy()
g = df.groupby(by)
for c in cols:
result[c] = df[c] - g[c].transform('mean')
return result
Затем можно записать как:
result = (df[df.col1 < 0]
.pipe(group_demean, ['key1', 'key2'], ['col1']))
Как и многие другие открытые проекты, pandas продолжает развиваться и совершенствоваться. Как и в других местах этой книги, здесь акцент делается на том, что в ближайшие несколько лет не произойдёт никаких изменений и функционал будет стабильным.
Для глубокого изучения pandas я рекомендую изучать официальную документацию и следить за обновлениями документации, публикуемыми командой разработчиков. Мы также приглашаем вас присоединиться к разработке pandas: исправлять ошибки, создавать новые функции и улучшать документацию.
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )