Reputation: 53
I have the coordinates of 3D box (oriented in the same referential and with coordinates min and max on XYZ) in the dataframe BOX And coordinate XYZ of a points in dataframe PTS
BOX = pd.DataFrame({'Box':['BA', 'BB', 'BC'], 'Box-Xmin':[1, 2, 30], 'Box-Ymin':[1,2,30], 'Box-Zmin':[1,2,30], 'Box-Xmax':[2,4,60], 'Box-Ymax':[2,4,60], 'Box-Zmax':[2,4,60]})
PTS = pd.DataFrame({'Point':['P1', 'P2', 'P3'], 'Pt-X':[0, 45, 20], 'Pt-Y':[8,33,40], 'Pt-Z':[5,30,50]})
I try to make a merge, join between this both dataframe when the point is in the BOX like:
Box Point
BA
BB
BC P2
BC P3
I have check on geo pandas side but it's more for 2D coordinates.
Thanks :)
Upvotes: 1
Views: 458
Reputation: 5451
you can also do it with numpy as below, not sure if it performs better but I think it should
BOX = pd.DataFrame({'Box':['BA', 'BB', 'BC'], 'Box-Xmin':[1, 2, 30], 'Box-Ymin':[1,2,30], 'Box-Zmin':[1,2,30], 'Box-Xmax':[50,4,60], 'Box-Ymax':[50,4,60], 'Box-Zmax':[50,4,60]})
PTS = pd.DataFrame({'Point':['P1', 'P2', 'P3'], 'Pt-X':[7, 45, 40], 'Pt-Y':[8,33,40], 'Pt-Z':[5,30,60]})
l_box = BOX[["Box-Xmin","Box-Ymin","Box-Zmin"]].values
u_box = BOX[["Box-Xmax","Box-Ymax","Box-Zmax"]].values
## add new dimension to use broadcast to compare all pairs of box and points
pts = PTS[["Pt-X","Pt-Y","Pt-Z"]].values[:, np.newaxis, :]
# this is convenient expression but the block below will use less memory
# cond = np.all((l_box<= pts) & (pts<=u_box), axis=2)
cond1 = np.greater_equal(pts, l_box)
cond2 = np.greater_equal(u_box, pts)
np.logical_and(cond1, cond2, out=cond1)
cond2 = None
cond = np.all(cond1, axis=2)
# fancy index for points falling inside box
pts_ind = np.arange(len(pts)).reshape(-1,1).repeat(np.sum(cond, axis=1))
# fancy index of box having points
box_ind = np.arange(len(l_box)).repeat(np.sum(cond, axis=0))
# finally create dataframe using the numpy array
pd.DataFrame(dict(Box=BOX["Box"].values[box_ind],
Point=PTS["Point"].values[pts_ind]))
Result
**Input**
Box Box-Xmin Box-Ymin Box-Zmin Box-Xmax Box-Ymax Box-Zmax
0 BA 1 1 1 50 50 50
1 BB 2 2 2 4 4 4
2 BC 30 30 30 60 60 60
Point Pt-X Pt-Y Pt-Z
0 P1 7 8 5
1 P2 45 33 30
2 P3 40 40 60
**Result**
Box Point
0 BA P1
1 BA P2
2 BC P2
3 BC P3
Upvotes: 1
Reputation: 150745
If you data are not too big, you can use broadcasting:
# for convenience
bmins = BOX.filter(like='min').values
bmaxs = BOX.filter(like='max').values
coords = PTS.filter(like='Pt').values
xy = np.nonzero(((coords[:,None, :] >= bmins) & # compare each coordinate with min
(coords[:,None, :] <= bmaxs) # compare each coordinate with max
).all(-1) # make sure all comparisons are True
) # nonzero collects only True values
new_df = pd.DataFrame({'Box':BOX.loc[xy[1],'Box'].values,
'Point':PTS.loc[xy[0],'Point'].values})
Output:
Box Point
0 BC P2
1 BC P3
which you can merge to BOX
for your desired output, e.g:
BOX[['Box']].merge(new_df, on='Box', how='left')
gives:
Box Point
0 BA NaN
1 BB NaN
2 BC P2
3 BC P3
Upvotes: 2