Я должен объединить два ДФ. Один из них является моим основным, другой имеет много 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.