Я должен объединить два 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.
Я предполагаю, что вы хотите объединить две строки с одинаковыми значениями столбцов (игнорируйте NaN). Если данные не велики, это можно сделать с помощью двух циклов for.
Попробуйте функцию комбинированный_первый
>>> df1 = pd.DataFrame([[1, np.nan]])
>>> df2 = pd.DataFrame([[3, 4]])
>>> df1.combine_first(df2)
0 1
0 1 4.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')
Не хватает репутации, чтобы комментировать, но почему бы не использовать
df.dropna()
Затем попытаться объединить кадры данных?
r_id
?