Векторизованное слияние двух панд. DataFrame, по диапазону идентификаторов - PullRequest
0 голосов
/ 15 марта 2019

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


DataFrame A: 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 в A (чьи ID s попадают в range(min_matchID, max_matchID+1))
  • и создайте экземпляр класса MultiPolygon (как в пакете Python shapely), например,
MultiPolygon([box(332833.5, 502144.0, 333214.5, 502460.5), box(333537.5, 502144.0, 333918.5, 502460.5)])

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

1 Ответ

1 голос
/ 15 марта 2019

Сначала вы можете использовать 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)]))
...