Объединить 2 кадра данных с ненулевыми значениями - PullRequest
0 голосов
/ 08 января 2019

Я должен объединить два ДФ. Один из них является моим основным, другой имеет много NaN

образец df1:

code        hotel_region   hotel_country        chain_name   brand_name
9737              EUROPE       ESTONIA        Bridgestreet        NaN
5397       LATIN AMERICA    COSTA RICA         Independent   No Brand
2392       LATIN AMERICA         ARUBA        DIVI RESORTS        NaN
9776       LATIN AMERICA        BRAZIL         Independent   W Hotels
4720       LATIN AMERICA     ARGENTINA         Independent   No Brand

образец df2:

r_id  hotel_region    hotel_country                   chain_name     brand_name
78   LATIN AMERICA         HONDURAS     Barcelo Hotels and Resorts        NaN
92   LATIN AMERICA     SANDWICH ISL     Barcelo Hotels and Resorts        NaN
151            NaN              NaN                   Bridgestreet        NaN
117  NORTH AMERICA           CANADA                Magnuson Hotels        NaN
47   LATIN AMERICA           BRAZIL                            NaN   W Hotels 

Результат, который я хотел бы получить, примерно такой:

code   hotel_region   hotel_country     chain_name   brand_name  r_id
9737         EUROPE       ESTONIA     Bridgestreet        NaN     151
9776  LATIN AMERICA        BRAZIL      Independent   W Hotels      47

Слияние должно просто "игнорировать" значения NaN и объединяться только тогда, когда значение столбца не является NaN. Я пробовал разные вещи, однако данные в df2 имеют десятки возможностей, где могут появляться значения NaN. У df1 есть 168 тыс. строк, а у df2 примерно 170, и r_id должен быть связан с любым code, который соответствует всем не-NaN значениям. У кого-нибудь есть идеи о том, как сделать это эффективно?

После обширных исследований различных подходов кажется, что "магического" способа игнорировать NaN, вероятно, не существует. Я думал о том, чтобы применить маску к df2 и разделить на группы, просмотреть их, объединить каждую группу с df1 и впоследствии удалить дубликаты. То есть здесь я бы имел

(True, True, True,  True, False),
(True, False, False, True, False),
(True, True, True, False, True)

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

Редактировать - как я решил эту проблему

В итоге я изучил подход, описанный выше - применил маску к df2, разделил ее по маске, объединил с df1.

Шаг 1: создать маску

masked = df2[['hotel_region', 'hotel_country', 'chain_name', 'brand_name']]

mask = pd.notnull(masked)

Шаг 2: группа df, в соответствии с NaN (= False) значениями

    group_mask = mask.groupby(['hotel_region','hotel_country', 'chain_name','brand_name']).count().reset_index()

Шаг 3: добавить группы столбцов в df2 в массив split_groups в соответствии со значениями true / false в group_mask

split_groups = []

for index, row in group_mask.iterrows():
    bool_groups = []
    # If the whole group is False, then cannot be taken in consideration, 
    # as it would result in a merge on the whole df1
    if not any(row.to_dict().values()):
        pass
    else:
        bool_groups.append(
                [key for key in row.to_dict().keys() if row.to_dict()[key] == False])
        bool_groups.append(
                [key for key in row.to_dict().keys() if row.to_dict()[key] == True])
        split_groups.append(bool_groups)

Шаг 4: создать массив разделенных dfs по столбцам в df2, где все значения не равны False

mps = []
"""
First, we extract rows where i[0] is null. In the resulting df, we extract rows
where i[1] is not null. Then, we drop all columns with na values. In this way
we retain only columns good for the merge. 
"""
for i in split_groups:
    df = df2[(df2[i[0]].isnull()).all(1)]
    df = df[(df[i[1]].notnull()).all(1)]
    df = df.dropna(axis='columns', how='all')
    mps.append(df)

Шаг 5: перебрать массив и объединить 2 DFS в соответствии с существующими столбцами

merged_dfs = []

for i in range(len(mps)):
    merged_dfs.append(df1.merge(mps[i], on=(split_groups[i][1]), how='left'))

Шаг 6: concat dfs в merged_dfs

merged_df = pd.concat(merged_dfs, sort=False)

Шаг 7: отбросить дубликаты

merged_df = merged_df.drop_duplicates()

Шаг 8 вызывает merged_df.columns.tolist() и сохраняет только те столбцы, которые полезны для конечного результата.

Я думаю, что этот подход не оптимален - если у кого-то есть идеи о том, как сделать это более эффективным, я буду очень признателен. Спасибо @ qingshan за предложение о зацикливании, он дал мне подсказку, чтобы в конечном итоге перебирать различные списки dfs.

Ответы [ 4 ]

0 голосов
/ 08 января 2019

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

0 голосов
/ 08 января 2019

Вы можете объединить отфильтрованные кадры данных, чтобы получить то, что вам нужно. Используйте это, чтобы отфильтровать ваш фрейм данных и затем выполнить слияние влево, чтобы получить выходные данные.

out_df = df1[~df1.isnull().T.any().T].merge(df2[~df2.isnull().T.any().T], on=['hotel_region', 'hotel_country', 'chain_name', 'brand_name'], how='left')
0 голосов
/ 08 января 2019

Попробуйте комбинированную первую функцию

http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.combine_first.html#pandas.DataFrame.combine_first

>>> df1 = pd.DataFrame([[1, np.nan]])
>>> df2 = pd.DataFrame([[3, 4]])
>>> df1.combine_first(df2)

   0    1
0  1  4.0
0 голосов
/ 08 января 2019

Не хватает репутации, чтобы комментировать, но почему бы не использовать

df.dropna()

Тогда попробуйте объединить кадры данных?

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