High Level Explanation
This answer is very much in demand. Below are the high level points
- Use
np.searchsorted with west latitudes in A relative to sorted latitudes in B Repeat this for eastern latitudes, but with the side='right' parameter. This tells me where every west-east latitude falls in the spectrum defined in B np.searchsorted - index values. Therefore, if the results in the east are greater than the results in the west, this means that there was a latitude from B between the west and the east.- Track where the eastern search exceeds the western search.
- Find the difference. Not only does it
B.Lat me if the eastern search exceeds the western, the difference tells me how much B.Lat is between them. I am expanding arrays to track all possible matches.
- Repeat this process for the southern and northern longitudes, but only over the subset in which we found the corresponding latitudes. No need to spend effort looking where we know that we have no match.
- Perform complex slicing to unwind positions.
- Use
numpy structured arrays to find intersections of two-dimensional arrays - This last intersection has ordinal positions from
A and matching ordinal positions from B- Remember, I said that we can match more than one line from
B , I take the first.
Demonstration
use A and B provided by @MaxU
p = process(A, B) print(p) [[3 0] [5 1] [9 2]]
To show results and compare. I'll put them side by side
pd.concat([ A.iloc[p[:, 0]].reset_index(drop=True), B.iloc[p[:, 1]].reset_index(drop=True) ], axis=1, keys=['A', 'B'])

However, to replace type values, I put a flag in the process function to reassign. Just run process(A, B, reassign_type=True)
time
Functions
I defined functions to return the same thing and make a comparison of apples with apples
def pir(A, B): return A.values[process(A, B)[:, 0], 0] def maxu(A, B): return B.apply( lambda x: A.loc[ (A.latw < x.Lat) & (x.Lat < A.late) & (A.lngs < x.Lng) & (x.Lng < A.lngn), 'type' ].head(1), axis=1 ).values.diagonal()

more rigorous testing
from timeit import timeit from itertools import product rng = np.arange(20, 51, 5) idx = pd.MultiIndex.from_arrays([rng, rng], names=['B', 'A']) functions = pd.Index(['pir', 'maxu'], name='method') results = pd.DataFrame(index=idx, columns=functions) for bi, ai in idx: n = ai ws = np.random.rand(n, 2) * 10 + np.array([[40, 30]]) A = pd.DataFrame( np.hstack([-np.ones((n, 1)), ws, ws + np.random.rand(n, 2) * 2]), columns=['type', 'latw', 'lngs', 'late', 'lngn'] ) m = int(bi ** .5) prng = np.arange(m) / m * 10 + .5 B = pd.DataFrame( np.array(list(product(prng, prng))) + np.array([[40, 30]]), columns=['Lat', "Lng"]) B.insert(0, 'type', np.random.randint(3, size=len(B))) for j in functions: results.set_value( (bi, ai), j, timeit( '{}(A, B)'.format(j), 'from __main__ import {}, A, B'.format(j), number=10 ) ) results.plot(rot=45)

Code
from collections import namedtuple TwoSided = namedtuple('TwoSided', ['match', 'idx', 'found']) def two_sided_search(left, right, a):