1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/it-ebooks-pyda-2e-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
12.md 23 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 28.11.2024 16:49 0ce2ae9
Категории (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

Глава 12. Продвинутое использование GroupBy

Хотя мы подробно изучили методы Series и DataFrame GroupBy в главе 10, есть ещё несколько полезных методов.

Групповое преобразование и «распаковка» GroupBy

В главе 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.

12.3 Методы программирования цепочек

При выполнении ряда преобразований над набором данных вы можете обнаружить, что созданные временные переменные фактически не используются в анализе. Рассмотрим следующий пример:

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']))

12.4 Заключение

Как и многие другие открытые проекты, pandas продолжает развиваться и совершенствоваться. Как и в других местах этой книги, здесь акцент делается на том, что в ближайшие несколько лет не произойдёт никаких изменений и функционал будет стабильным.

Для глубокого изучения pandas я рекомендую изучать официальную документацию и следить за обновлениями документации, публикуемыми командой разработчиков. Мы также приглашаем вас присоединиться к разработке pandas: исправлять ошибки, создавать новые функции и улучшать документацию.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/it-ebooks-pyda-2e-zh.git
git@api.gitlife.ru:oschina-mirror/it-ebooks-pyda-2e-zh.git
oschina-mirror
it-ebooks-pyda-2e-zh
it-ebooks-pyda-2e-zh
master