Группировка и A) Конкретные совпадающие строки (и / или подстрока) B) Суммируйте значения - PullRequest
1 голос
/ 29 марта 2020

У меня есть df:

row_numbers    ID     code        amount
   1           med    a           1
   2           med    a, b        1
   3           med    b, c        1
   4           med    c           1
   5           med    d           10
   6           cad    a, b        1 
   7           cad    a, b, d     0
   8           cad    e           2

Вставил вышеупомянутый df:

enter image description here

Я хотел выполнить групповую обработку в столбце- ID и A) Объединить строки, если подстрока / строка соответствует (в столбце- код ) B) суммировать значения столбца- сумму .

Ожидаемые результаты:

enter image description here

Объяснение:

column- row_numbers здесь не играет роли в df. Я просто взял здесь, чтобы объяснить вывод.

A) группировка по столбцу- ID и просмотр столбца- код , строка строки1, т. Е. A соответствует подстроке строки2. подстрока row2, т.е. b совпадает с подстрокой row3. Подстрока строки 3, т.е. c совпадает со строкой строки 4 и, следовательно, объединяет строки 1, 2, 2 и 3. Строка row5 не совпадает ни с одной строкой / подстрокой, поэтому это отдельная группа. B) На основе этого добавление значений row1, row2, row3 и row4. и row5 как отдельная группа.

Заранее спасибо за ваше время и мысли:).

РЕДАКТИРОВАТЬ - 1

Вставка в реальном времени.

enter image description here

Ожидаемый результат:

enter image description here

Объяснение:

приходится делать на группирование столбца- id и объединение значений столбца- код и суммирование значений столбца- единиц и том . Он имеет цветовую кодировку совпадающих (с которыми нужно связаться) значений столбца - код . row1 имеет связь с row5 и row9. row9 имеет внутреннюю связь с row3. Следовательно, объединяя row1, row5, row9, row3. Одновременно row2 и row7 и так далее. row8 не имеет связи ни с одним из значений, входящих в group-med (column-id), и, следовательно, будет отдельной строкой.

Спасибо!.

Ответы [ 2 ]

2 голосов
/ 31 марта 2020

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

Рассмотрим каждую строку как node графика. Если 2 строки перекрываются, они являются связанными узлами. Для каждого node вам необходимо пройти все пути, связанные с ним. Выполните расчет на всех подключенных узлах через эти пути. Это можно сделать с помощью Поиск в глубину logi c.

Однако перед обработкой поиска в глубину необходимо предварительно обработать строки до set для проверки перекрытия.

Метод 1: Рекурсивный

Выполните следующие действия:

  • Определите функцию dfs для рекурсивного запуска поиска в глубину
  • Определите функцию gfunc для использования с groupby apply. Эта функция будет проходить элементы каждой группы ID и возвращать нужный кадр данных.
  • Избавьтесь от любых пробелов в каждой строке, разделите и преобразуйте их в наборы, используя replace, split и map, и назначьте его новому столбцу new_code в df
  • Вызовите группу по ID и apply, используя функцию gfunc. Позвоните droplevel и reset_index, чтобы получить желаемый выходной код

следующим образом:

import numpy as np

def dfs(node, index, glist, g_checked_rows):        
    ret_arr = df.loc[index, ['code', 'amount', 'volume']].values
    g_checked_rows.add(index)   
    for j, s in glist:
        if j not in g_checked_rows and not node.isdisjoint(s):      
            t_arr = dfs(s, j, glist, g_checked_rows)
            ret_arr[0]  += ', ' + t_arr[0]
            ret_arr[1:] += t_arr[1:]

    return ret_arr

def gfunc(x):   
    checked_rows = set()    
    final = []  
    code_list = list(x.new_code.items())
    for i, row in code_list:
        if i not in checked_rows:       
            final.append(dfs(row, i, code_list, checked_rows))

    return pd.DataFrame(final, columns=['code','units','vol'])

df['new_code'] = df.code.str.replace(' ','').str.split(',').map(set)
df_final = df.groupby('ID', sort=False).apply(gfunc).droplevel(1).reset_index()

Out[16]:
    ID                                        code  units  vol
0  med  CO-96, CO-B15, CO-B15, CO-96, OA-18, OA-18      4    4
1  med                        CO-16, CO-B20, CO-16      3    3
2  med                       CO-252, CO-252, CO-45      3    3
3  med                                      OA-258      1    1
4  cad                        PR-96, PR-96, CO-243      4    4
5  cad                        PR-87, OA-258, PR-87      3    3

Примечание. Я полагаю, что ваша версия pandas равна 0,24+ , Если это <0,24, последний шаг вам нужно использовать <code>reset_index и drop вместо droplevel и reset_index следующим образом

df_final = df.groupby('ID', sort=False).apply(gfunc).reset_index().drop('level_1', 1)

Метод 2: Итеративный

Чтобы завершить это, я реализую версию gfunc, используя итеративный процесс вместо рекурсивного. Итеративный процесс требует только одну функцию. Однако функция более сложная. Логика c итерационного процесса выглядит следующим образом:

  1. pu sh с первого узла на deque. Проверьте, не является ли deque пустым, выдвиньте верхний узел.
  2. , если узел не помечен checked, обработайте его и пометьте как checked
  3. , чтобы найти всех его соседей в обратный порядок списка узлов, которые не были помечены, выведите sh их на deque
  4. Проверьте, не является ли deque пустым, выскочите узел сверху deque и обработайте с шага 2

Код следующий:

def gfunc_iter(x):  
    checked_rows = set()    
    final = []  
    q = deque() 
    code_list = list(x.new_code.items())
    code_list_rev = code_list[::-1]
    for i, row in code_list:
        if i not in checked_rows:           
            q.append((i, row))
            ret_arr = np.array(['', 0, 0], dtype='O')
            while (q):
                n, node = q.pop()
                if n in  checked_rows:
                    continue                
                ret_arr_child = df.loc[n, ['code', 'amount', 'volume']].values
                if not ret_arr[0]:
                    ret_arr = ret_arr_child.copy()
                else:
                    ret_arr[0]  += ', ' + ret_arr_child[0]
                    ret_arr[1:] += ret_arr_child[1:]                    
                checked_rows.add(n)         
                #push to `q` all neighbors in the reversed list of nodes    
                for j, s in code_list_rev: 
                    if j not in checked_rows and not node.isdisjoint(s):
                        q.append((j, s))
            final.append(ret_arr)

    return pd.DataFrame(final, columns=['code','units','vol'])

df['new_code'] = df.code.str.replace(' ','').str.split(',').map(set)
df_final = df.groupby('ID', sort=False).apply(gfunc_iter).droplevel(1).reset_index()    
1 голос
/ 31 марта 2020

Я считаю, что три основные идеи для выполнения того, что вы хотите:

  1. создать структуру данных аккумулятора (в данном случае DataFrame)
  2. выполнить итерацию по паре строк, в каждая ваша итерация (currentRow, nextRow)
  3. сопоставление с образцом текущей строки в следующей строке и сопоставление с образцом в накопленных строках

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

, используя в качестве примера data.csv (с разделителями пробелов):

row_numbers ID code amount
1 med a 1
2 med a,b 1
3 med b,c 1
4 med c 1
5 med d 10
6 cad a,b 1
7 cad a,b,d 0
8 cad e 2
import pandas as pd
from itertools import zip_longest

def generate_pairs(group):    
    ''' generate pairs (currentRow, nextRow) '''
    group_curriterrows = group.iterrows()
    group_nextiterrows = group.iterrows()
    group_nextiterrows.__next__()
    zip_list = zip_longest(group_curriterrows, group_nextiterrows) 
    return zip_list

def generate_lists_to_check(currRow, nextRow, accumulated_rows):
    ''' generate list if any next letters are in current ones and
    another list if any next letters are in the accumulated codes '''
    currLetters = str(currRow["code"]).split(",")
    nextLetters = str(nextRow["code"]).split(",")
    letter_inNext = [letter in nextLetters for letter in currLetters]
    unique_acc_codes = [str(v) for v in accumulated_rows["code"].unique()]
    letter_inHistory = [any(letter in unq for letter in nextLetters) 
                                           for unq in unique_acc_codes]
    return letter_inNext, letter_inHistory

def create_newRow(accumulated_rows, nextRow):
  nextRow["row_numbers"] = str(nextRow["row_numbers"])
  accumulated_rows = accumulated_rows.append(nextRow,ignore_index=True)
  return accumulated_rows

def update_existingRow(accumulated_rows, match_idx, Row):
  accumulated_rows.loc[match_idx]["code"] += ","+Row["code"]
  accumulated_rows.loc[match_idx]["amount"] += Row["amount"]
  accumulated_rows.loc[match_idx]["volume"] += Row["volume"]
  accumulated_rows.loc[match_idx]["row_numbers"] += ','+str(Row["row_numbers"])
  return accumulated_rows

if __name__ == "__main__":
    df = pd.read_csv("extended.tsv",sep=" ")
    groups = pd.DataFrame(columns=df.columns) 
    for ID, group in df.groupby(["ID"], sort=False):
        accumulated_rows = pd.DataFrame(columns=df.columns)
        group_firstRow = group.iloc[0]
        accumulated_rows.loc[len(accumulated_rows)] = group_firstRow.values 
        row_numbers = str(group_firstRow.values[0])
        accumulated_rows.set_value(0,'row_numbers',row_numbers) 
        zip_list = generate_pairs(group)
        for (currRow_idx, currRow), Next  in zip_list:
            if not (Next is None):
                (nextRow_idx, nextRow) = Next
                letter_inNext, letter_inHistory = \
                        generate_lists_to_check(currRow, nextRow, accumulated_rows) 

                if any(letter_inNext) :
                  accumulated_rows = update_existingRow(accumulated_rows, (len(accumulated_rows)-1), nextRow) 
                elif any(letter_inHistory):
                  matches = [ idx for (idx, bool_val) in enumerate(letter_inHistory) if bool_val == True ]
                  first_match_idx = matches[0]
                  accumulated_rows = update_existingRow(accumulated_rows, first_match_idx, nextRow)
                  for match_idx in matches[1:]:
                      accumulated_rows = update_existingRow(accumulated_rows, first_match_idx, accumulated_rows.loc[match_idx])
                      accumulated_rows = accumulated_rows.drop(match_idx) 

                elif not any(letter_inNext):
                  accumulated_rows = create_newRow(accumulated_rows, nextRow)
        groups = groups.append(accumulated_rows)
    groups.reset_index(inplace=True,drop=True)
    print(groups)

ВЫХОДНЫЕ обычные строки упорядочивают УДАЛЯЮЩИЕ строки, используя объем столбца из текущего кода, потому что первый экзамен не имеет объема столбца:

  row_numbers   ID         code amount
0           1  med  a,a,b,b,c,c      4
1           5  med            d     10
2           6  cad    a,b,a,b,d      1
3           8  cad            e      2

Новый пример ВЫХОДА:

  row_numbers   ID                                   code amount volume
0     1,5,9,3  med  CO-96,CO-B15,CO-B15,CO-96,OA-18,OA-18      4      4
1         2,7  med                     CO-16,CO-B20,CO-16      3      3
2         4,6  med                    CO-252,CO-252,CO-45      3      3
3           8  med                                 OA-258      1      1
4       10,13  cad                     PR-96,PR-96,CO-243      4      4
5       11,12  cad                     PR-87,OA-258,PR-87      3      3
...