Объединить 2 кадра данных с ненулевыми значениями

1

Я должен объединить два DF. Один мой главный дф, у другого много 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: создать массив разделенных df2 по столбцам в df2 где все значения не являются ложными

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: merged_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.

  • 0
    Обязательно ли иметь r_id ?
  • 0
    к сожалению да, это параметр, с помощью которого я должен помечать строки в df1, которые связаны с df2 :-)
Теги:
pandas
dataframe

4 ответа

0
Лучший ответ

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

0

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

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
    уже пробовал, но вывод противоречивый / не правильный. Он объединяет строки, которые не должны касаться
0

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

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
    к сожалению, с этим решением я получаю только значения в столбцах из df2 после слияния
  • 0
    Это работало в наборе данных, который вы дали выше. Пожалуйста, предоставьте больше данных для работы
0

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

df.dropna()

Затем попытаться объединить кадры данных?

Ещё вопросы

Сообщество Overcoder
Наверх
Меню