Соответствие и условный взрыв в pandas - PullRequest
3 голосов
/ 04 февраля 2020

Я столкнулся с трудной проблемой, которую нужно решить, прежде чем взорваться

Моя проблема лучше всего описывается данными. Это выглядит так:

df = pd.DataFrame({
    'A': [
        [0.05, 0.055, 0.055, 0.06, 0.065, 0.07, 0.075, 0.075, 0.085, 0.09, 1.32],
        [0.4, 0.06, 0.06, 0.13, 0.135, 0.145, 0.155, 0.17] , 
        [3.81, 0.3, 0.4, 0.425, 0.445, 0.48, 0.51, 0.54, 0.58, 0.62, 0.66, 0.66, 0.705, 0.53, 0.57, 0.61], 
        [7.395, 0.075, 0.085, 0.09, 0.095, 0.1, 0.11, 0.12, 0.13, 0.14],
        [0.105, 0.11, 0.12, 0.125, 0.135, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.205, 2.21]
    ], 
    'B' : [
        [0.680, 1.320],
        [0.520, 0.130, 0.135, 0.145, 0.155, 0.170],
        [8.035, 3.810],
        [0.945, 7.395],
        [1.790, 2.210]
    ],
    'C' : [
        ['08/01/91', '08/01/10'],
        ['09/01/92', '09/01/93', '09/01/94', '09/01/95', '09/01/96', '09/01/10'],
        ['11/01/91', '11/01/10'],
        ['09/01/93', '09/01/21'],
        ['12/01/92', '12/01/10']
    ]
})
df

A   B   C
0   [0.05, 0.055, 0.055, 0.06, 0.065, 0.07, 0.075, 0.075, 0.085, 0.09, 1.32]    [0.68, 1.32]    [08/01/91, 08/01/10]
1   [0.4, 0.06, 0.06, 0.13, 0.135, 0.145, 0.155, 0.17]  [0.52, 0.13, 0.135, 0.145, 0.155, 0.17] [09/01/92, 09/01/93, 09/01/94, 09/01/95, 09/01/96, 09/01/10]
2   [3.81, 0.3, 0.4, 0.425, 0.445, 0.48, 0.51, 0.54, 0.58, 0.62, 0.66, 0.66, 0.705, 0.53, 0.57, 0.61]   [8.035, 3.81]   [11/01/91, 11/01/10]
3   [7.395, 0.075, 0.085, 0.09, 0.095, 0.1, 0.11, 0.12, 0.13, 0.14] [0.945, 7.395]  [09/01/93, 09/01/21]
4   [0.105, 0.11, 0.12, 0.125, 0.135, 0.14, 0.15, 0.16, 0.17, 0.18, 0.19, 0.205, 2.21]  [1.79, 2.21]    [12/01/92, 12/01/10]

Гарантируется, что сумма элементов списка в A равна сумме элементов списка в B. Обычно они упорядочены, но есть случаи, когда оно отменяется.

Например, в случае, например, строки 0, первые 10 элементов суммируют до 0,68, а 1,32 сопоставляются по порядку.

Однако, строка 2 - наоборот, так как 3.81 соответствует последнему элементу B. Столбцы B & C относятся к одному и тому же набору данных, поэтому они должны быть перенесены, чтобы соответствовать порядку A.

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

      A         B        C
0   0.05      0.68    08/01/91 
0   0.055     0.68    08/01/91 
0   0.055     0.68    08/01/91 
0   0.06      0.68    08/01/91 
0   0.065     0.68    08/01/91 
0   0.07      0.68    08/01/91 
0   0.075     0.68    08/01/91 
0   0.085     0.68    08/01/91 
0   0.09      0.68    08/01/91 
0   1.32      1.32    08/01/10 
...
2   3.81      3.81    11/01/10
2   0.3       8.035   11/01/91         
2   0.4       8.035   11/01/91         
2   0.425     8.035   11/01/91           
2   0.445     8.035   11/01/91           
2   0.48      8.035   11/01/91          
2   0.51      8.035   11/01/91         
2   0.54      8.035   11/01/91          
2   0.58      8.035   11/01/91   
2   0.62      8.035   11/01/91   
2   0.66      8.035   11/01/91   
2   0.66      8.035   11/01/91   
2   0.705     8.035   11/01/91    
2   0.52      8.035   11/01/91   
2   0.57      8.035   11/01/91   
2   0.61      8.035   11/01/91   

Любые идеи и подходы высоко ценятся.

Я обнаружил, что допустил ошибку в приведенных выше данных и исправил ее. B & C у них всегда есть точное количество элементов в их списке.

Случай строки 1: мой желаемый результат будет:

1   0.4       0.520   09/01/92                   
1   0.06      0.520   09/01/92               
1   0.06      0.520   09/01/92               
1   0.13      0.130   09/01/93               
1   0.135     0.135   09/01/94                
1   0.145     0.145   09/01/95                
1   0.155     0.155   09/01/96                
1   0.17      0.17    09/01/10     

Ответы [ 2 ]

1 голос
/ 09 февраля 2020

Основная идея c в следующем примере состоит в том, чтобы создать «совпадающие» списки одинаковой длины в каждом столбце для каждой строки сначала в данных, а затем «транспонировать» (разнесение здесь не совсем правильно) этих списков. , Это также может легко масштабироваться для большего количества столбцов, позвольте мне сейчас, если вам нужна помощь по обобщению функции

def match_row(row):
    bc_mapping = {b: c for b, c in zip(row['B'], row['C'])}
    common_elements = set(row['A']).intersection(set(row['B']))
    sum_elements = set(row['B']).difference(common_elements)
    assert len(sum_elements) == 1  # Sanity check

    common_elements = sorted(common_elements)
    sum_element = list(sum_elements)[0]
    number_of_free_elements = len(row['A']) - len(common_elements)

    return pd.Series({
         'A': [element for element in row['A'] if element not in common_elements] + common_elements,
         'B': [sum_element] * number_of_free_elements + common_elements,
         'C': [bc_mapping[sum_element]] * number_of_free_elements + [bc_mapping[element] for element in common_elements]
    })


df = df. \
    apply(match_row, axis=1). \
    aggregate('sum'). \
    apply(pd.Series). \
    transpose()

РЕДАКТИРОВАТЬ: Обобщение для столбцов с несколькими столбцами: Случай нескольких столбцов не так тривиально, но должно работать следующее:

df['D'] = df['B'].apply(lambda b: [random.randint(0, 10) for _ in b])

def match_row(row, variable_column, reference_column):
    fixed_columns = row.index.tolist()
    fixed_columns.remove(variable_column)
    fixed_columns.remove(reference_column)

    variable_elements = row[variable_column]
    reference_elements = row[reference_column]
    fixed_elements = row[fixed_columns].apply(pd.Series).T.values.tolist()

    fixed_elements_mapping = {
        reference: fixed_elements
        for reference, fixed_elements
        in zip(reference_elements, fixed_elements)
    }
    common_elements = set(variable_elements).intersection(set(reference_elements))
    sum_elements = set(reference_elements).difference(common_elements)
    assert len(sum_elements) == 1  # Sanity check

    common_elements = sorted(common_elements)
    sum_element = list(sum_elements)[0]
    number_of_free_elements = len(variable_elements) - len(common_elements)

    variable_and_reference_result = pd.Series({
        variable_column: [element for element in row['A'] if element not in common_elements] + common_elements,
        reference_column: [sum_element] * number_of_free_elements + common_elements,
    })
    fixed_coluumns_result = pd.Series({
        column_name: [fixed_elements_mapping[sum_element][i]] * number_of_free_elements +
                     [fixed_elements_mapping[element][i] for element in common_elements]
        for i, column_name
        in enumerate(fixed_columns)
    })
    return pd.concat([variable_and_reference_result, fixed_coluumns_result])


df = df. \
    apply(lambda row: match_row(row, 'A', 'B'), axis=1). \
    aggregate('sum'). \
    apply(pd.Series). \
    transpose()
1 голос
/ 04 февраля 2020

Я чувствую, что правильный, менее мерзкий способ достижения вашей цели - это выйти из Pandas в Python. Здесь идет код с комментариями по сторонам. Дай мне знать, если это работает с твоей стороны, как и ожидалось. С моей стороны все работало нормально:

M = df.to_dict('records') #return dataframe as a dictionary

Вот где происходит вычисление:

from operator import itemgetter


coll=[]

for (i,j) in enumerate(M):

    #get value(s) that are in both  lists
    yes = set(M[i]['A'])&set(M[i]['B'])

    #lump B and C, we need the date included   
    merge = list(zip(M[i]['B'],M[i]['C'])) 

    #give values that are in the yes set
    yes_filter = [r for r in merge if r[0] in yes] 

    #give values that are not in the yes set
    no_filter = [r for r in merge if r[0] not in yes] 

    #create box to house items in 'A' column that are not in the 'yes' set 
    #the indexing will help in sorting the values before
    #putting them back into the dataframe
    no_content = [(p,j) for (j,p) in enumerate(M[i]['A']) if p not in yes] 

    #reverse of line above
    yes_content = [(p,j) for (j,p) in enumerate(M[i]['A']) if p in yes] 

    #lump no content and no_filter
    nawano = [(q,s,*f) for q,s,f in [(*q,*no_filter) for q in no_content]] 

    #lump yes content and yes filter
    nawayes = [(*p,*q) for p,q in zip(yes_content,yes_filter)] 

    nawano.extend(nawayes)

    #we use the index to sort the list 
    nawano = sorted(nawano,  key=itemgetter(1)) 

    coll.extend(nawano) 

Теперь мы можем вернуться к Pandas:

#create dataframe
outcome = pd.DataFrame(coll, columns = ['A','temp','B','C']).drop('temp',axis=1) 

outcome.head()

      A      B         C
0   0.050   0.68    08/01/91
1   0.055   0.68    08/01/91
2   0.055   0.68    08/01/91
3   0.060   0.68    08/01/91
4   0.065   0.68    08/01/91

Я уверен, что это можно оптимизировать. Если вы найдете лучшее решение, пожалуйста, дайте мне знать. Я бы тоже хотел учиться. Приветствия.

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