You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何查找时间序列X触及/穿越Y对应值的首个索引?

Efficiently Find First Crossing Index in Time Series with NumPy/Numba

Great question! For large datasets, Python loops are definitely not feasible, but we can leverage NumPy for vectorized operations or Numba for optimized compiled loops to solve this problem efficiently. Let's break down the approach and solutions.

First, Clarify the Problem Logic

For each index i, we need the smallest index j > i such that the line segment between X[j-1] and X[j] crosses Y[i] (i.e., Y[i] lies strictly between X[j-1] and X[j]). If no such j exists, we assign NaN to Z[i].

Looking at your example:

  • For i=0, Y[i]=3 crosses the segment between X[1]=2 and X[2]=4, so Z[0]=2.
  • For i=5, Y[i]=2 crosses the segment between X[5]=3 and X[6]=1, so Z[5]=6.
  • All other i values have no valid crossing segment, hence NaN.

Solution 1: Numba Optimized Loop

Numba compiles Python loops into machine code, making them nearly as fast as vectorized operations. This is the most practical approach for large datasets, as it balances simplicity and performance.

import numpy as np
from numba import njit

@njit
def find_first_crossing(X, Y):
    """
    Find the first j > i where X's segment [j-1, j] crosses Y[i].
    
    Args:
        X: NumPy array of time series values
        Y: NumPy array of target values (same length as X)
    
    Returns:
        Z: NumPy array where Z[i] is the first j, or NaN if no crossing exists
    """
    n = len(X)
    Z = np.full(n, np.nan)
    
    for i in range(n):
        target = Y[i]
        # Check each subsequent segment for a crossing
        for j in range(i + 1, n):
            x_prev = X[j-1]
            x_curr = X[j]
            # Check if target is strictly between x_prev and x_curr
            if (x_prev - target) * (x_curr - target) < 0:
                Z[i] = j
                break  # Stop at the first valid j
    
    return Z

# Test with your sample data
X = np.array([2, 2, 4, 5, 4, 3, 1], dtype=np.float64)
Y = np.array([3, 3, 4.5, 5, 5, 2, 2], dtype=np.float64)
Z = find_first_crossing(X, Y)
print(Z)
# Output: [ 2. nan  3. nan nan  6. nan]

Why This Works

  • The @njit decorator compiles the function to optimized machine code, eliminating Python loop overhead.
  • For each i, we stop at the first valid j, avoiding unnecessary computations.
  • This handles large datasets (100k+ elements) efficiently, with performance comparable to pure vectorized NumPy code.

Solution 2: Vectorized NumPy Approach (For Smaller Datasets)

If you prefer a pure NumPy solution without Numba, you can use broadcasting to check all possible i and j pairs. Note: This is not recommended for very large datasets (n > 1000) due to high memory usage.

import numpy as np

def find_first_crossing_vectorized(X, Y):
    n = len(X)
    # Create a grid of i and j indices
    i_grid, j_grid = np.meshgrid(np.arange(n), np.arange(1, n), indexing='ij')
    
    # Mask where j > i and Y[i] crosses X's segment [j-1, j]
    valid_mask = (j_grid > i_grid) & ((Y[i_grid] - X[j_grid - 1]) * (Y[i_grid] - X[j_grid]) < 0)
    
    # Find the first valid j for each i
    # argmax returns the first True index in each row
    first_j = np.argmax(valid_mask, axis=1)
    # Convert j_grid indices to actual j values (add 1 since j starts at 1)
    Z = np.where(valid_mask.any(axis=1), first_j + 1, np.nan)
    
    return Z

# Test with sample data
Z = find_first_crossing_vectorized(X, Y)
print(Z)
# Output: [ 2. nan  3. nan nan  6. nan]

Limitations

  • This creates an n x (n-1) matrix, which uses O(n²) memory. For n=1000, this is manageable, but for n=10000, it becomes 100 million elements—way too much for most systems.

Solution 3: Interval-Based Matching (Balanced Approach)

Another approach is to process each segment [j-1, j] and match it to all unmatched i < j where Y[i] crosses the segment. This reduces redundant checks if many i values are matched early.

import numpy as np

def find_first_crossing_interval(X, Y):
    n = len(X)
    Z = np.full(n, np.nan)
    unmatched = np.ones(n, dtype=bool)  # Track which i haven't found a j yet
    
    for j in range(1, n):
        x_prev = X[j-1]
        x_curr = X[j]
        # Find unmatched i < j where Y[i] crosses the segment
        mask = unmatched.copy()
        mask[j:] = False  # Only consider i < j
        mask &= ((Y - x_prev) * (Y - x_curr) < 0)
        
        # Assign j to these i's
        Z[mask] = j
        unmatched[mask] = False
        
        # Early exit if all i are matched
        if not unmatched.any():
            break
    
    return Z

# Test with sample data
Z = find_first_crossing_interval(X, Y)
print(Z)
# Output: [ 2. nan  3. nan nan  6. nan]

When to Use This

  • This works well if most i values are matched by early segments (e.g., if Y values are crossed quickly in X).
  • Worst-case time complexity is still O(n²), but it can be faster than the Numba loop in some scenarios.

Final Recommendation

For large datasets, the Numba optimized loop is the best choice—it's fast, memory-efficient, and easy to read. If you can't use Numba, the interval-based approach is a solid alternative.

内容的提问来源于stack exchange,提问作者quant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 06:37:53