Как я могу складывать строки, которые имеют какое-либо общее значение столбца?

#python #pandas

#python #pandas

Вопрос:

Я не уверен, что формулировка заголовка оптимальна, потому что проблему, с которой я столкнулся, немного сложно объяснить. В коде у меня есть df, который выглядит примерно так:

 import pandas as pd
import numpy as np 
a = ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'D', 'E', 'E']
b = [3, 1, 2, 3, 12, 4, 7, 8, 3, 10, 12]
df = pd.DataFrame([a, b]).T

df
  

Выдает

     0   1
0   A   3
1   A   1
2   A   2
3   B   3
4   B  12
5   B   4
6   C   7
7   C   8
8   D   3
9   E  10
10  E  12
  

Мне известны методы groupby для группировки по значениям в столбце, но это не совсем то, что я хочу. Я вроде как хочу сделать шаг дальше, когда любое пересечение в столбце 1 между группами столбца 0 группируется вместе. Моя формулировка ужасна (вероятно, поэтому у меня возникают проблемы с вводом этого в код), но вот в основном то, что я хочу в качестве вывода:

     0   1
0   A-B-D-E   3
1   A-B-D-E   1
2   A-B-D-E   2
3   A-B-D-E   3
4   A-B-D-E  12
5   A-B-D-E   4
6   C   7
7   C   8
8   A-B-D-E   3
9   A-B-D-E  10
10  A-B-D-E  12
  

По сути, A, B и D имеют общее значение 3 в столбце 1, поэтому их метки группируются вместе в столбце 0. Теперь, поскольку B и E имеют общее значение 12 в столбце 1, а B разделяет значение 3 в столбце 1 с A и D, E также группируется с A, B и D . Единственное значение в столбце 0, которое осталось независимым, — это C, поскольку оно не пересекается ни с какой другой группой.

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

Комментарии:

1. Обратите внимание, что A, B, C, D являются произвольными представлениями значений в 0. Мне не нужно, чтобы имя было в каком-либо алфавитном порядке или что-либо еще.

Ответ №1:

Если кто-нибудь в будущем столкнется с тем же, это сработает (хотя, возможно, это не лучшее решение в мире):

 import pandas as pd 
import numpy as np
a = ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'D', 'E', 'E']
b = ['3', '1', '2', '3', '12', '4', '7', '8', '3', '10', '12']
df = pd.DataFrame([a, b]).T
df.columns = 'a', 'b'
df2 = df.copy()

def flatten(container):
    for i in container:
        if isinstance(i, (list,tuple)):
            for j in flatten(i):
                yield j
        else:
            yield i

bad = True
i =1
while bad:
    print("Round " str(i))
    i = i 1
    len_checker = []
    for variant in list(set(df.a)):
        eGenes = list(set(df.loc[df.a==variant, 'b']))
        inter_variants = []
        for gene in eGenes:
            inter_variants.append(list(set(df.loc[df.b==gene, 'a'])))
        if type(inter_variants[0]) is not str:
           inter_variants = [x for x in flatten(inter_variants)]
        inter_variants = list(set(inter_variants))
        len_checker.append(inter_variants)
        if len(inter_variants) != 1:
            df2.loc[df2.a.isin(inter_variants),'a']='-'.join(inter_variants)
    good_checker = max([len(x) for x in len_checker])
    df['a'] = df2.a
    if good_checker == 1:
        bad=False

df.a = df.a.apply(lambda x: '-'.join(list(set(x.split('-')))))
df.drop_duplicates(inplace=True)
  

Ответ №2:

Следующее создает желаемый результат без рекурсий. Я не тестировал это с другими созвездиями (другой порядок, больше комбинаций и т.д.).

 a = ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'D', 'E', 'E']
b = [3, 1, 2, 3, 12, 4, 7, 8, 3, 10, 12]
df = list(zip(a, b))

print(df)


class Bucket:
    def __init__(self, keys, values):
        self.keys = set(keys)
        self.values = set(values)

    def contains_key(self, key):
        return key in self.keys

    def add_if_contained(self, key, value):
        if value in self.values:
            self.keys.add(key)
            return True
        elif key in self.keys:
            self.values.add(value)
            return True
        return False

    def merge(self, bucket):
        self.keys.update(bucket.keys)
        self.values.update(bucket.values)

    def __str__(self):
        return f'{self.keys} :: {self.values}>'

    def __repr__(self):
        return str(self)


res = []
for tup in df:
    added = False
    if res:
        selected_bucket = None
        remove_idx = None
        for idx, bucket in enumerate(res):
            if not added:
                added = bucket.add_if_contained(tup[0], tup[1])
                selected_bucket = bucket
            elif bucket.contains_key(tup[0]):
                selected_bucket.merge(bucket)
                remove_idx = idx

        if remove_idx is not None:
            res.pop(remove_idx)

    if not added:
        res.append(Bucket({tup[0]}, {tup[1]}))

print(res)
  

Генерирует следующий вывод:

 $ python test.py 
[('A', 3), ('A', 1), ('A', 2), ('B', 3), ('B', 12), ('B', 4), ('C', 7), ('C', 8), ('D', 3), ('E', 10), ('E', 12)]
[{'B', 'D', 'A', 'E'} :: {1, 2, 3, 4, 10, 12}>, {'C'} :: {8, 7}>]