Как объединить множество комбинаций категорий (72 переменных) в списках python

#python

#python

Вопрос:

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

 lst = [
      ['FHControl', G, A]
      ['MNHDosed', G, C]
      ]
 

Для строки в lst: row[0] всего 12 категорий (я перечислил две в примере кода выше). Для строк [1] и строк [2] Меня интересуют только 6 комбинаций этих букв. Таким образом, у меня есть 72 возможных комбинации этих данных для каждой строки в lst, и мне нужно подсчитать экземпляры каждой комбинации без необходимости писать десятки вложенных циклов if.

Я пытаюсь создать две функции для анализа этих списков и сопоставления случаев этих 72 комбинаций. Как я могу использовать две функции, подобные тем, что я начинаю писать ниже, для обновления этих переменных? Нужно ли мне создавать словари как переменные класса, чтобы я мог продолжать обновлять их по мере выполнения обеих функций? Любое руководство было бы здорово!

Вот код, который у меня есть в настоящее время, который инициализирует все 72 переменные в 6 словарях (для 6 комбинаций букв в строке [1] и строке [2]):

 def baseparser(lst):
    TEMP = dict.fromkeys('FHDosed FHControl FNHDosed FNHControl '
                         'FTDosed FTControl MHDosed MHControl '
                         'MNHDosed MNHControl MTDosed MTControl'.split(), 0)
    TRI_1, TRI_2, TRV_1, TRV_2, TRV_3, TRV_4 = ([dict(TEMP) for i in range(6)])

    for row in lst:
        if row[0] == 'FHDosed':
            binner(row[0], row[1], row[2])
        if row[0] == 'FHControl':
            binner(row[0], row[1], row[2])
        etc.

def binner(key, q, s):
    if (q == 'G' and s == 'A') or (q =='C' and s =='T'):
        TRI_1[key]  = 1
    elif (q == 'A' and s == 'G') or (q =='T' and s =='C'):
        TRI_2[key]  = 1
    elif (q == 'G' and s == 'T') or (q =='C' and s =='A'):
        TRV_1[key]  = 1
    elif (q == 'G' and s == 'C') or (q =='C' and s =='G'):
        TRV_1[key]  = 1
    elif (q == 'A' and s == 'T') or (q =='T' and s =='A'):
        TRV_1[key]  = 1
    elif (q == 'A' and s == 'C') or (q =='T' and s =='G'):
        TRV_1[key]  = 1
 

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

1. binner Зависит от двух аргументов или трех? Если это зависит только от q и s которые выглядят как однобуквенные строки, то зачем вам нужны if-операторы, например if row[0] == 'FHDosed' , перед вызовом binner , и не должно ли это быть binner(row[1], row[2]) вместо binner(row[0], row[1], row[2]) ?

2. Ах, вы правы. Я не был ясен. Я чувствую, что binner должен зависеть от трех аргументов, первый из которых определяет, какой ключ в соответствующем словаре должен быть вызван для изменения значения ( =1 для каждого экземпляра). Вторые два аргумента используются циклом if, чтобы выбрать, с какого словаря выбирается начать. Я отредактирую свой код, чтобы отразить.

3. Кажется, нет никакой разницы между вызовом binner независимо от того, что row[0] равно. Есть ли детали, которые вы упускаете?

4. @clintval будет ли для вас использование pandas ? Это было сделано именно для этих типов операций.

Ответ №1:

Ваш код может быть упрощен до:

 TEMP = dict.fromkeys('''FHDosed FHControl FNHDosed FNHControl FTDosed FTControl MHDosed 
                      MHControl MNHDosed MNHControl MTDosed MTControl'''.split(), 0)
TRI_1, TRI_2, TRV_1, TRV_2, TRV_3, TRV_4 = [TEMP.copy() for i in range(6)]

dmap = {
    ('G', 'A'): TRI_1,
    ('C', 'T'): TRI_1,
    ('A', 'G'): TRI_2,
    ('T', 'C'): TRI_2,        
    ('G', 'C'): TRV_1,
    ('C', 'G'): TRV_1,        
    ('A', 'T'): TRV_1,
    ('T', 'A'): TRV_1,        
    }

for row in lst:
    key, q, s = row
    dmap[q, s][key]  = 1
 

Другая возможность — использовать один dict of dicts вместо 6 dicts:

 TEMP = dict.fromkeys('''FHDosed FHControl FNHDosed FNHControl FTDosed FTControl MHDosed 
                      MHControl MNHDosed MNHControl MTDosed MTControl'''.split(), 0)
TR = {key:TEMP.copy() for key in ('TRI_1', 'TRI_2', 'TRV_1', 'TRV_2', 'TRV_3', 'TRV_4')}


dmap = {
    ('G', 'A'): 'TRI_1',
    ('C', 'T'): 'TRI_1',
    ('A', 'G'): 'TRI_2',
    ('T', 'C'): 'TRI_2', 
    ('G', 'C'): 'TRV_1',
    ('C', 'G'): 'TRV_1', 
    ('A', 'T'): 'TRV_1',
    ('T', 'A'): 'TRV_1',        
    }

lst = [
      ['FHControl', 'G', 'A'],
      ['MNHDosed', 'G', 'C']
      ]

for row in lst:
    key, q, s = row
    TR[dmap[q, s]][key]  = 1

print(TR)
 

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


Следуя предложению Полуночника, если у вас есть pandas, вы можете заменить dict of dicts на DataFrame . Тогда частота пар может быть вычислена с использованием pd.crosstabs следующим образом:

 import pandas as pd

dmap = {
    'GA': 'TRI_1',
    'CT': 'TRI_1',
    'AG': 'TRI_2',
    'TC': 'TRI_2', 
    'GC': 'TRV_1',
    'CG': 'TRV_1', 
    'AT': 'TRV_1',
    'TA': 'TRV_1',        
    }

lst = [
      ['FHControl', 'G', 'A'],
      ['MNHDosed', 'G', 'C']
      ]

df = pd.DataFrame(lst, columns=['key', 'q', 's'])
df['tr'] = (df['q'] df['s']).map(dmap)

print(df)
#          key  q  s     tr
# 0  FHControl  G  A  TRI_1
# 1   MNHDosed  G  C  TRV_1

print(pd.crosstab(rows=[df['key']], cols=[df['tr']]))
 

дает

 tr         TRI_1  TRV_1
key                    
FHControl      1      0
MNHDosed       0      1
 

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

1. Это сработало! Это упростит расширение скрипта в будущем. Я изучу Pandas … после небольшого чтения у меня может быть больше приложений, для которых Pandas идеально подойдут. Слава!