Векторизованное слияние двух `pandas.DataFrame`, по диапазону идентификаторов

#python #pandas #merge #range

#python #pandas #слияние #диапазон

Вопрос:

У меня есть два DataFrame файла, и я хотел бы выполнить некоторую операцию, используя их оба в качестве входных данных.


DataFrame Ответ: x1 , y1 x2 , y2 ,,,,,,,,,,, соответствуют координатам прямоугольника

  --- ---- ---------- ---------- ---------- ---------- 
|   | ID |    x1    |    y1    |    x2    |    y2    |
 --- ---- ---------- ---------- ---------- ---------- 
| 0 |  0 | 332833.5 | 502144.0 | 333214.5 | 502460.5 |
| 1 |  1 | 333537.5 | 502144.0 | 333918.5 | 502460.5 |
| 2 |  2 | 334945.5 | 502144.0 | 335326.5 | 502352.0 |
| 3 |  3 | 335713.5 | 502144.0 | 336094.5 | 502352.0 |
| 4 |  4 | 336417.5 | 502144.0 | 336798.5 | 502416.0 |
...
 --- ---- ---------- ---------- ---------- ---------- 
  

DataFrame B:

  --- ------------- ------------- -- -- 
|   | min_matchID | max_matchID |  |  |
 --- ------------- ------------- -- -- 
| 0 |           0 |           1 |  |  |
| 1 |           2 |           2 |  |  |
| 2 |           3 |           5 |  |  |
| 3 |           6 |           7 |  |  |
| 4 |           8 |           8 |  |  |
...
 --- ------------- ------------- -- -- 
  

Для каждой строки записи в B с идентификатором между min_matchID и max_matchID , я хотел бы:

  • запросите соответствующую коллекцию x1 , y1 x2 y2 , ID range(min_matchID, max_matchID 1) в A (в который, как правило, входят в,,)
  • и создайте MultiPolygon экземпляр класса (как в пакете python shapely ), например
 MultiPolygon([box(332833.5, 502144.0, 333214.5, 502460.5), box(333537.5, 502144.0, 333918.5, 502460.5)])
  

Перебор для цикла очевиден, но это просто слишком медленно. Интересно, есть ли векторизованный способ сделать это?

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

1. Вы профилировали свой код, чтобы увидеть, где происходит замедление? Я подозреваю, что создание экземпляра MultiPolygon обходится дороже, чем вы думаете.

2. @AndrewGuy Мой %lprun показывает, что MultiPolygon создание экземпляра на самом деле сильно отстает от Парето, занимая ~ 5% от общего времени выполнения. 70% времени выполнения занимает запрос (я использую df.iterrows() и df.query() ).

3. В качестве стилистического совета, чтобы сделать код намного понятнее, я бы назвал A ‘pts’ и B ‘ids’. (Тогда я бы переименовал столбцы B просто min и max . ids['min'] ... ids['max'] кратко, ясно и не требует пояснений.

4. @smci большое вам спасибо за предложение. Это отличная идея. Но поскольку уже есть подробный ответ, в котором используется текущая нотация, я не буду обновлять его, чтобы избежать путаницы для других читателей.

Ответ №1:

Сначала вы можете использовать Index.repeat для повторения строк на основе ваших min_matchID и max_matchID .

 import pandas as pd
import numpy as np
from shapely.geometry import MultiPolygon,box
# generate test data
A = pd.DataFrame({'ID':range(0,10000),'x1':range(10000,20000),'y1': range(50000, 60000)
                 ,'x2': range(10000, 20000), 'y2': range(50000, 60000)})
B = pd.DataFrame({'min_matchID':np.random.randint(0,10000,size=(10000))})
B['max_matchID'] = B['min_matchID']   np.random.randint(0,10,size=(10000))

# start 
B = B.reset_index()
idx = B.index.repeat(B.max_matchID - B.min_matchID   1)
B = B.reindex(idx).reset_index(drop=True)
B['ID'] =  B['min_matchID']   idx.to_series().groupby(idx).cumcount().values
print(B)

       index  min_matchID  max_matchID    ID
0          0         6889         6891  6889
1          0         6889         6891  6890
2          0         6889         6891  6891
3          1         8299         8307  8299
4          1         8299         8307  8300
5          1         8299         8307  8301
6          1         8299         8307  8302
7          1         8299         8307  8303
...      ...          ...          ...   ...
54740   9998         4278         4282  4282
54741   9999         3061         3067  3061
54742   9999         3061         3067  3062
54743   9999         3061         3067  3063
54744   9999         3061         3067  3064
54745   9999         3061         3067  3065
54746   9999         3061         3067  3066
54747   9999         3061         3067  3067
  

Затем вы можете попробовать pd.merge() объединить координаты.

 result = pd.merge(B,A,on='ID',how='left')
print(result)
       index  min_matchID  max_matchID    ID       x1       y1       x2       y2
0          0         6889         6891  6889  16889.0  56889.0  16889.0  56889.0
1          0         6889         6891  6890  16890.0  56890.0  16890.0  56890.0
2          0         6889         6891  6891  16891.0  56891.0  16891.0  56891.0
3          1         8299         8307  8299  18299.0  58299.0  18299.0  58299.0
4          1         8299         8307  8300  18300.0  58300.0  18300.0  58300.0
5          1         8299         8307  8301  18301.0  58301.0  18301.0  58301.0
6          1         8299         8307  8302  18302.0  58302.0  18302.0  58302.0
7          1         8299         8307  8303  18303.0  58303.0  18303.0  58303.0
...      ...          ...          ...   ...      ...      ...      ...      ...
54740   9998         4278         4282  4282  14282.0  54282.0  14282.0  54282.0
54741   9999         3061         3067  3061  13061.0  53061.0  13061.0  53061.0
54742   9999         3061         3067  3062  13062.0  53062.0  13062.0  53062.0
54743   9999         3061         3067  3063  13063.0  53063.0  13063.0  53063.0
54744   9999         3061         3067  3064  13064.0  53064.0  13064.0  53064.0
54745   9999         3061         3067  3065  13065.0  53065.0  13065.0  53065.0
54746   9999         3061         3067  3066  13066.0  53066.0  13066.0  53066.0
54747   9999         3061         3067  3067  13067.0  53067.0  13067.0  53067.0
  

Наконец, вы можете сгруппировать по index , чтобы добиться этого.

 result = result.groupby('index').apply(lambda x:MultiPolygon([box(x1,y1,x2,y2) for x1,y1,x2,y2 in zip(x.x1,x.y1,x.x2,x.y2)]))
  

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

1. Большое вам спасибо за эти усилия! merge кажется отличной идеей ~ Есть только одно предостережение — вероятно, мне следовало более четко указать в моем посте — что min_matchID и max_matchID относится к диапазону (от min_matchID до max_matchID ). Таким образом, может быть более 2 строк A, которые необходимо объединить.

2. @XiUpsilon Я отредактировал ответы в соответствии с вашими новыми вопросами.