Рецепт для группировки / агрегирования данных? - PullRequest
0 голосов
/ 29 апреля 2018

У меня есть некоторые данные, хранящиеся в списке, которые я хотел бы сгруппировать по значению.

Например, если мои данные

data = [(1, 'a'), (2, 'x'), (1, 'b')]

и я хочу сгруппировать его по первому значению в каждом кортеже, чтобы получить

result = [(1, 'ab'), (2, 'x')]

как бы мне поступить?

В целом, каков рекомендуемый способ группировки данных в python? Есть рецепт, который может мне помочь?

Ответы [ 4 ]

0 голосов
/ 02 июня 2018

Pandas groupby

Это не рецепт как таковой, а интуитивно понятный и гибкий способ группировки данных с помощью функции. В этом случае функция str.join.

import pandas as pd

data = [(1, 'a'), (2, 'x'), (1, 'b')]

# create dataframe from list of tuples
df = pd.DataFrame(data)

# group by first item and apply str.join
grp = df.groupby(0)[1].apply(''.join)

# create list of tuples from index and value
res = list(zip(grp.index, grp))

print(res)

[(1, 'ab'), (2, 'x')]

Преимущества

  • Прекрасно вписывается в рабочие процессы, которые требуют только вывода list в конце последовательности векторизованных шагов.
  • Легко адаптируется путем изменения ''.join на list или другой уменьшающей функции.

Недостатки

  • Избыток для изолированного задания: требуется list -> pd.DataFrame -> list преобразование.
  • Вводит зависимость от сторонней библиотеки.
0 голосов
/ 29 апреля 2018

itertools.groupby

Существует рецепт общего назначения в itertools, и это groupby().

Схема этого рецепта может быть дана в следующем виде:

[(k, aggregate(g)) for k, g in groupby(sorted(data, key=extractKey), extractKey)]

Две важные части, которые нужно изменить в рецепте:

  • определить ключ группировки ( extractKey ): в этом случае получить первый элемент кортежа:

    lambda x: x[0]

  • агрегированные сгруппированные результаты (при необходимости) ( агрегат ): g содержит все соответствующие кортежи для каждого ключа k (например, (1, 'a'), (1, 'b') для ключа 1 и (2, 'x') для ключа 2), мы хотим взять только второй элемент кортежа и объединить все эти элементы в одну строку:

    ''.join(x[1] for x in g)

* * Пример 1 042:
from itertools import groupby

extractKey = lambda x: x[0]
aggregate = lambda g: ''.join(x[1] for x in g)

[(k, aggregate(g)) for k, g in groupby(sorted(data, key=extractKey), extractKey)]
# [(1, 'ab'), (2, 'x')]

Иногда extractKey, aggregate или оба могут быть встроены в одну строку (мы также опускаем ключ сортировки, так как это избыточно для этого примера):

[(k, ''.join(x[1] for x in g)) for k, g in groupby(sorted(data), lambda x: x[0])]
# [(1, 'ab'), (2, 'x')]

Плюсы и минусы

Сравнение этого рецепта с рецептом с использованием defaultdict есть плюсы и минусы в обоих случаях.

groupby() имеет тенденцию быть медленнее (примерно в два раза медленнее в моих тестах), чем рецепт defaultdict.

С другой стороны, groupby() имеет преимущества в случае с ограниченным объемом памяти, когда значения создаются на лету; вы можете обрабатывать группы в потоковом режиме, не сохраняя их; defaultdict потребуется память для хранения всех из них.

0 голосов
/ 30 апреля 2018

Многократное понимание списка

Это неэффективно по сравнению с решениями dict и groupby.

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

res = [(i, ''.join([j[1] for j in data if j[0] == i]))
       for i in set(list(zip(*data))[0])]

[(1, 'ab'), (2, 'x')]

Решение можно разбить на 2 части:

  1. set(list(zip(*data))[0]) извлекает уникальный набор идентификаторов, который мы повторяем через цикл for в пределах понимания списка.
  2. (i, ''.join([j[1] for j in data if j[0] == i])) применяет логику, которая нам необходима для желаемого выхода.
0 голосов
/ 29 апреля 2018

Структура данных перехода, используемая для всех видов группировки, - dict . Идея состоит в том, чтобы использовать что-то, что однозначно идентифицирует группу как ключи dict, и хранить все значения, принадлежащие одной и той же группе, под одним и тем же ключом.

Например, ваши данные могут быть сохранены в таком виде:

{1: ['a', 'b'],
 2: ['x']}

Целое число, которое вы используете для группировки значений, используется в качестве ключа dict, а значения агрегируются в список.

Причина, по которой мы используем dict, заключается в том, что он может отображать ключи на значения за постоянное время O (1). Это делает процесс группировки очень эффективным, а также очень простым. Общая структура кода всегда будет одинаковой для всех видов задач группирования: вы перебираете свои данные и постепенно заполняете данные сгруппированными значениями. Использование defaultdict вместо обычного dict делает весь процесс еще проще, потому что нам не нужно беспокоиться об инициализации dict с пустыми списками.

import collections

groupdict = collections.defaultdict(list)
for value in data:
    group = value[0]
    value = value[1]
    groupdict[group].append(value)

# result:
# {1: ['a', 'b'],
#  2: ['x']}

Как только данные сгруппированы, остается только преобразовать dict в желаемый формат вывода:

result = [(key, ''.join(values)) for key, values in groupdict.items()]
# result: [(1, 'ab'), (2, 'x')]

Рецепт группировки

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

import collections

groupdict = collections.defaultdict(list)
for value in data:  # input
    group = ???  # group identifier
    value = ???  # value to add to the group
    groupdict[group].append(value)

result = groupdict  # output

Каждая из прокомментированных строк может / должна быть настроена в зависимости от вашего варианта использования.

Input

Формат ваших входных данных определяет способ итерации по ним.

В этом разделе мы настраиваем строку рецепта for value in data:.

  • Список значений

    Чаще всего все значения хранятся в плоском списке:

    data = [value1, value2, value3, ...]
    

    В этом случае мы просто перебираем список с циклом for:

    for value in data:
    
  • Несколько списков

    Если у вас есть несколько списков, каждый из которых содержит значение другого атрибута, например

    firstnames = [firstname1, firstname2, ...]
    middlenames = [middlename1, middlename2, ...]
    lastnames = [lastname1, lastname2, ...]
    

    используйте функцию zip для итерации по всем спискам одновременно:

    for value in zip(firstnames, middlenames, lastnames):
    

    Это сделает value кортеж (firstname, middlename, lastname).

  • Несколько диктов или список диктов

    Если вы хотите объединить несколько диктовок, таких как

    dict1 = {'a': 1, 'b': 2}
    dict2 = {'b': 5}
    

    Сначала поместите их все в список:

    dicts = [dict1, dict2]
    

    И затем использовать два вложенных цикла для итерации по всем (key, value) парам:

    for dict_ in dicts:
        for value in dict_.items():
    

    В этом случае переменная value примет форму двухэлементного кортежа, например ('a', 1) или ('b', 2).

Группировка

Здесь мы рассмотрим различные способы извлечения идентификаторов групп из ваших данных.

В этом разделе мы настраиваем строку рецепта group = ???.

  • Группировка по элементу list / tuple / dict

    Если ваши значения представляют собой списки или кортежи, такие как (attr1, attr2, attr3, ...), и вы хотите сгруппировать их по n-му элементу:

    group = value[n]
    

    Синтаксис для диктов одинаков, поэтому если у вас есть такие значения, как {'firstname': 'foo', 'lastname': 'bar'}, и вы хотите сгруппировать по имени:

    group = value['firstname']
    
  • Группировка по атрибуту

    Если ваши значения являются объектами типа datetime.date(2018, 5, 27) и вы хотите сгруппировать их по атрибуту, например year:

    group = value.year
    
  • Группировка по ключевой функции

    Иногда у вас есть функция, которая возвращает группу значения при его вызове. Например, вы можете использовать функцию len для группировки значений по их длине:

    group = len(value)
    
  • Группировка по нескольким значениям

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

    group = (value[0], len(value))
    
  • Группировка по чему-то неуловимому

    Поскольку ключи dict должны быть хешируемыми , у вас возникнут проблемы, если вы попытаетесь сгруппировать что-то, что не может быть хешировано. В таком случае вам нужно найти способ преобразовать неискажаемое значение в хеш-представление.

    1. наборы : преобразование наборов в frozensets , которые можно хэшировать:

      group = frozenset(group)
      
    2. dicts : Dicts могут быть представлены как отсортированные (key, value) кортежи:

      group = tuple(sorted(group.items()))
      

Изменение агрегированных значений

Иногда вам может понадобиться изменить значения, которые вы группируете. Например, если вы группируете кортежи, такие как (1, 'a') и (1, 'b'), по первому элементу, вы можете удалить первый элемент из каждого кортежа, чтобы получить результат, подобный {1: ['a', 'b']}, а не {1: [(1, 'a'), (1, 'b')]}.

В этом разделе мы настраиваем строку value = ??? рецепта.

  • Без изменений

    Если вы не хотите изменять значение каким-либо образом, просто удалите строку value = ??? из своего кода.

  • Хранение только одного элемента list / tuple / dict

    Если ваши значения представляют собой списки, такие как [1, 'a'], и вы хотите сохранить только 'a':

    value = value[1]
    

    Или, если они диктуют, как {'firstname': 'foo', 'lastname': 'bar'}, и вы хотите оставить только имя:

    value = value['firstname']
    
  • Удаление первого элемента списка / кортежа

    Если ваши значения являются списками, такими как [1, 'a', 'foo'] и [1, 'b', 'bar'], и вы хотите отбросить первый элемент каждого кортежа, чтобы получить группу, подобную [['a', 'foo], ['b', 'bar']], используйте синтаксис срезов:

    value = value[1:]
    
  • Удаление / Сохранение произвольных элементов списка / кортежа / dict

    Если ваши значения являются списками, такими как ['foo', 'bar', 'baz'], или диктантами, такими как {'firstname': 'foo', 'middlename': 'bar', 'lastname': 'baz'}, и вы хотите удалить или оставить только некоторые из этих элементов, начните с создания набора элементов, которые вы хотите сохранить или удалить. Например:

    indices_to_keep = {0, 2}
    keys_to_delete = {'firstname', 'middlename'}
    

    Затем выберите соответствующий фрагмент из этого списка:

    1. Для сохранения элементов списка: value = [val for i, val in enumerate(value) if i in indices_to_keep]
    2. Чтобы удалить элементы списка: value = [val for i, val in enumerate(value) if i not in indices_to_delete]
    3. Чтобы сохранить элементы dict: value = {key: val for key, val in value.items() if key in keys_to_keep]
    4. Чтобы удалить элементы dict: value = {key: val for key, val in value.items() if key not in keys_to_delete]

выход

Как только группировка завершена, у нас есть defaultdict, заполненный списками. Но желаемый результат не всегда (по умолчанию) диктует.

В этом разделе мы настраиваем строку result = groupdict рецепта.

  • Обычный дикт

    Чтобы преобразовать defaultdict в обычный dict, просто вызовите для него конструктор dict:

    result = dict(groupdict)
    
  • Список (group, value) пар

    Чтобы получить результат, подобный [(group1, value1), (group1, value2), (group2, value3)] из dict {group1: [value1, value2], group2: [value3]}, используйте список понимания :

    result = [(group, value) for group, values in groupdict.items()
                               for value in values]
    
  • Вложенный список просто значений

    Чтобы получить результат, подобный [[value1, value2], [value3]] из условия {group1: [value1, value2], group2: [value3]}, используйте dict.values:

    result = list(groupdict.values())
    
  • Плоский список просто значений

    Чтобы получить результат, подобный [value1, value2, value3] из dict {group1: [value1, value2], group2: [value3]}, сгладьте его с помощью списка :

    result = [value for values in groupdict.values() for value in values]
    
  • Сведение итерируемых значений

    Если ваши значения являются списками или другими итерациями, такими как

    groupdict = {group1: [[list1_value1, list1_value2], [list2_value1]]}
    

    и вы хотите получить плоский результат, такой как

    result = {group1: [list1_value1, list1_value2, list2_value1]}
    

    у вас есть два варианта:

    1. Сгладить списки с помощью диктовок :

      result = {group: [x for iterable in values for x in iterable]
                                for group, values in groupdict.items()}
      
    2. Во-первых, избегайте создания списка итераций, используя list.extend вместо list.append. Другими словами, изменить

      groupdict[group].append(value)
      

      до

      groupdict[group].extend(value)
      

      А затем просто установите result = groupdict.

  • отсортированный список

    Dicts - неупорядоченные структуры данных. Если вы выполняете итерацию по dict, вы никогда не знаете, в каком порядке будут перечислены его элементы. Если вы не заботитесь о заказе, вы можете использовать рецепты, показанные выше. Но если вы действительно заботитесь о порядке, вы должны соответствующим образом отсортировать вывод.

    Я буду использовать следующую команду, чтобы продемонстрировать, как сортировать вывод различными способами:

    groupdict = {'abc': [1], 'xy': [2, 5]}
    

    Имейте в виду, что это немного мета-рецепт, который, возможно, придется сочетать с другими частями этого ответа, чтобы получить именно тот результат, который вы хотите. Общая идея состоит в том, чтобы отсортировать ключи словаря перед их использованием для извлечения значений из dict:

    groups = sorted(groupdict.keys())
    # groups = ['abc', 'xy']
    

    Имейте в виду, что sorted принимает ключевую функцию в случае, если вы хотите настроить порядок сортировки. Например, если ключи dict являются строками и вы хотите отсортировать их по длине:

    groups = sorted(groupdict.keys(), key=len)
    # groups = ['xy', 'abc']
    

    После того, как вы отсортировали ключи, используйте их для извлечения значений из dict в правильном порядке:

    # groups = ['abc', 'xy']
    result = [groupdict[group] for group in groups]
    # result = [[1], [2, 5]]
    

    Помните, что это может быть объединено с другими частями этого ответа, чтобы получить различные виды вывода. Например, если вы хотите сохранить идентификаторы группы:

    # groups = ['abc', 'xy']
    result = [(group, groupdict[group]) for group in groups]
    # result = [('abc', [1]), ('xy', [2, 5])]
    

    Для вашего удобства вот несколько часто используемых порядков сортировки:

    1. Сортировка по количеству значений в группе:

       groups = sorted(groudict.keys(), key=lambda group: len(groupdict[group]))
       result = [groupdict[group] for group in groups]
       # result = [[2, 5], [1]]
      
  • Подсчет количества значений в каждой группе

    Для подсчета количества элементов, связанных с каждой группой, используйте функцию len:

    result = {group: len(values) for group, values in groupdict.items()}
    

    Если вы хотите подсчитать количество отдельных элементов, используйте set для устранения дубликатов:

    result = {group: len(set(values)) for group, values in groupdict.items()}
    

Пример

Чтобы продемонстрировать, как собрать воедино рабочее решение по этому рецепту, давайте попробуем включить

data = [["A",0], ["B",1], ["C",0], ["D",2], ["E",2]]

в

result = [["A", "C"], ["B"], ["D", "E"]]

Другими словами, мы группируем списки по их второму элементу.

Первые две строки рецепта всегда одинаковы, поэтому начнем с копирования:

import collections

groupdict = collections.defaultdict(list)

Теперь мы должны выяснить, как перебрать вход. Поскольку наш ввод представляет собой простой список значений, нормального цикла for будет достаточно:

for value in data:

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

    group = value[1]

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

    value = value[0]

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

result = list(groupdict.values())

Et voilà:

data = [["A",0], ["B",1], ["C",0], ["D",2], ["E",2]]

import collections

groupdict = collections.defaultdict(list)
for value in data:
    group = value[1]
    value = value[0]
    groupdict[group].append(value)

result = list(groupdict.values())
# result: [["A", "C"], ["B"], ["D", "E"]]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...