Объединение двух фреймов данных в Pandas на основе условий

#pandas #dataframe

#pandas #фрейм данных

Вопрос:

У меня есть 2 фрейма данных:

       siccd   date    retp  
0     2892  31135 -0.036296   
1     2892  31226  0.144768   
2     2892  31320  0.063924   
3     1650  31412 -0.009190   
4     1299  31502  0.063326   
 

и

     start   end  ind indname
0     100   999    1   Agric
1    1000  1299    2   Mines
2    1300  1399    3     Oil
3    1400  1499    4   Stone
4    1500  1799    5   Cnstr
5    2000  2099    6    Food
6    2100  2199    7   Smoke
7    2200  2299    8   Txtls
8    2300  2399    9   Apprl
9    2400  2499   10    Wood
10   2500  2599   11   Chair
11   2600  2661   12   Paper
12   2700  2799   13   Print
13   2800  2899   14   Chems
14   2900  2999   15   Ptrlm
15   3000  3099   16   Rubbr
16   3100  3199   17   Lethr
17   3200  3299   18   Glass
18   3300  3399   19  Metal 
 

Задача состоит в том, чтобы взять df1['siccd'] столбец, сравнить его со столбцом df2['start'] и df2['end'] . Если (start <= siccd <= end), присвоите значения ind и indname соответствующего индекса во втором фрейме данных первому фрейму данных. Результат будет выглядеть примерно так:

       siccd   date    retp   ind  indname
0     2892  31135 -0.036296  14   Chems
1     2892  31226  0.144768  14   Chems
2     2892  31320  0.063924  14   Chems
3     1650  31412 -0.009190  5    Cnstr
4     1299  31502  0.063326  2    Mines
 

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

 siccd_lst = list(tmp['siccd'])
ind_lst = []
indname_lst = []
def categorize(siccd, df, index):
    if (siccd >= df.iloc[index]['start']) and (siccd <= df.iloc[index]['end']):
        ind_lst.append(df.iloc[index]['ind'])
        indname_lst.append(df.iloc[index]['indname'])
    else:
        pass

for i in range(0, len(ff38.index)-1):
    [categorize(x, ff38, i) for x in siccd_lst]
 

Я также попытался векторизовать проблему, однако я не мог понять, как выполнить итерацию по всему df2 при «поиске» правильного ind и indname для присвоения первому фрейму данных.

Комментарии:

1. Я вырезал один из дополнительных столбцов, которые не имеют отношения к этому, но df1.shape выводит (413840, 21) и df2.shape выводит (41, 4). Спасибо!

2. Вы правы! Это было неправильно, я отредактировал его. Спасибо.

Ответ №1:

Интервалы

Мы создадим фрейм данных, где индекс — это интервал, а столбцы — значения, которые мы хотим сопоставить, затем мы можем использовать .loc этот фрейм данных для переноса данных.

Если какое-либо из ваших 'siccd' значений лежит за пределами всех интервалов, вы получите a KeyError , поэтому этот метод не будет работать.

 dfi = pd.DataFrame({'indname': df2['indname'].to_numpy(), 'ind': df2['ind'].to_numpy()}, 
                   index=pd.IntervalIndex.from_arrays(left=df2.start, right=df2.end, closed='both'))

df1[['indname', 'ind']] = dfi.loc[df1.siccd].to_numpy()
 

Слияние

Вы можете выполнить полное слияние (все строки в df1 со всеми строками в df2), используя временный столбец ( 't' ), а затем отфильтровать результат там, где он находится между значениями.

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

Если какое-либо из ваших 'siccd' значений лежит за пределами всех интервалов, строка из исходного фрейма данных будет удалена.

 res = (df1.assign(t=1)
          .merge(df2.assign(t=1), on='t', how='left')
          .query('siccd >= start amp; siccd <= end')
          .drop(columns=['t', 'start', 'end']))

#     siccd   date      retp  ind indname
#13   2892  31135 -0.036296   14   Chems
#32   2892  31226  0.144768   14   Chems
#51   2892  31320  0.063924   14   Chems
#61   1650  31412 -0.009190    5   Cnstr
#77   1299  31502  0.063326    2   Mines
 

Если вы ожидаете, что значения будут находиться за пределами некоторых интервалов, измените слияние. Приведите исходный индекс subset, который удаляет эти строки и использует combine_first их для добавления обратно после слияния. Я добавил строку в конце с символом 'siccd' of 252525 в качестве 6-й строки к вашему оригиналу df1 :

 res = (df1.reset_index().assign(t=1)
          .merge(df2.assign(t=1), on='t', how='left')
          .query('siccd >= start amp; siccd <= end')
          .drop(columns=['t', 'start', 'end'])
          .set_index('index')
          .combine_first(df1)  # Adds back rows, based on index,
      )                        # that were outside any Interval 

#      date   ind indname      retp     siccd
#0  31135.0  14.0   Chems -0.036296    2892.0
#1  31226.0  14.0   Chems  0.144768    2892.0
#2  31320.0  14.0   Chems  0.063924    2892.0
#3  31412.0   5.0   Cnstr -0.009190    1650.0
#4  31502.0   2.0   Mines  0.063326    1299.0
#5  31511.0   NaN     NaN  0.151341  252525.0