Python 熊猫(子)数据帧内的最大值和最小值

Python 熊猫(子)数据帧内的最大值和最小值,python,pandas,scikit-learn,Python,Pandas,Scikit Learn,我有以下数据帧-df: crs Band1 level lat lon 34.595694 32.929028 b'' 4.000000e+00 1000 32.937361 b'' 1.200000e+01 950 32.945694 b'' 2.900000e+01 925 34.604028 32.92

我有以下数据帧-
df

                     crs         Band1 level
lat       lon                               
34.595694 32.929028  b''  4.000000e+00  1000
          32.937361  b''  1.200000e+01  950
          32.945694  b''  2.900000e+01  925
34.604028 32.929028  b''  7.000000e+00  1000
          32.937361  b''  1.300000e+01  950
                 ...           ...   ...
71.179028 25.679028  b''  6.000000e+01  750
71.187361 25.662361  b''  1.000000e+00  725
          25.670694  b''  6.000000e+01  1000
          25.679028  b''  4.000000e+01  800
71.529028 19.387361  b''  1.843913e-38  1000

[17671817 rows x 3 columns]
和两个阵列:

lon1=np.arange(-11,47,0.25)
lat1=np.arange(71.5,34.5,-0.25)
这两个阵列(
lat1
lon1
)产生间隔为0.25度的坐标对

数据帧
df
包含点(
lat
lon
),这些点密集分布在用
lon1
lat1
数组定义的点内。我想做的是:

  • df
    中查找(过滤)与
    lat1
    lon1
    定义的点之间0.125度范围内的所有点
  • 从该子数据帧获取
    level
    max
    min
    值,并将其存储在与
    lon1
    lat1
    相同大小的单独数组中
  • 到目前为止,我所做的是筛选数据帧:

    for x1 in lon1:
        for y1 in lat1:
            df3=df[(df.index.get_level_values('lon')>x1-0.125) & (df.index.get_level_values('lon')<x1+0.125)]
            df3=df3[(df3.index.get_level_values('lat')>y1-0.125) & (df3.index.get_level_values('lat')<y1+0.125)]
    
    对于lon1中的x1:
    对于lat1中的y1:
    
    df3=df[(df.index.get_level_values('lon')>x1-0.125)和(df.index.get_level_values('lon')y1-0.125)和(df3.index.get_level_values('lat')在开始之前,让我们将您的箱子转换为每个箱子的起点,而不是中心:

    lon1=np.arange(-11.125,47.125,0.25)
    lat1=np.arange(71.625,34.125,-0.25)
    
    为每一行分配纬度和经度存储箱(注意
    lat1
    的相反顺序,否则需要将
    ordered=False
    传递给
    pd.cut()

    对于您的示例数据,我们现在有:

                         crs         Band1  level            latcat            loncat
    lat       lon                                                                    
    34.595694 32.929028  b''  4.000000e+00   1000  (34.375, 34.625]  (32.875, 33.125]
              32.937361  b''  1.200000e+01    950  (34.375, 34.625]  (32.875, 33.125]
              32.945694  b''  2.900000e+01    925  (34.375, 34.625]  (32.875, 33.125]
    34.604028 32.929028  b''  7.000000e+00   1000  (34.375, 34.625]  (32.875, 33.125]
              32.937361  b''  1.300000e+01    950  (34.375, 34.625]  (32.875, 33.125]
    71.179028 25.679028  b''  6.000000e+01    750  (71.125, 71.375]  (25.625, 25.875]
    71.187361 25.662361  b''  1.000000e+00    725  (71.125, 71.375]  (25.625, 25.875]
              25.670694  b''  6.000000e+01   1000  (71.125, 71.375]  (25.625, 25.875]
              25.679028  b''  4.000000e+01    800  (71.125, 71.375]  (25.625, 25.875]
    71.529028 19.387361  b''  1.843913e-38   1000  (71.375, 71.625]  (19.375, 19.625]
    
    现在使用groupby获取每个区域的最小和最大级别:

    res = df.groupby([df.latcat.cat.codes, df.loncat.cat.codes])['level'].agg(['min', 'max'])
    
    这给了你:

              min   max
    0   176   925  1000
    147 147   725  1000
    148 122  1000  1000
    
    索引的第一级是反向
    lat1
    数组中的位置,-1表示“超出范围”,您的一些示例数据就是这个范围。第二级是
    lon1
    数组中的位置

    要按要求转换为矩阵,请执行以下操作:

    minlevel = np.full((len(lat1), len(lon1)), np.nan)
    maxlevel = np.full((len(lat1), len(lon1)), np.nan)
    x = len(lat1) - res.index.get_level_values(0) - 1 # reverse to original order
    y = res.index.get_level_values(1)
    minlevel[x, y] = res['min']
    maxlevel[x, y] = res['max']
    

    首先让我们回顾一下您的解决方案:对于lon1中的每个值和lat1的每个值(如果它们的大小为n,则为n^2次迭代),您尝试过滤数据帧,这导致扫描整个df:您的代码在数据帧中运行了n^2次,这是低效的

    我的解决方案只需要扫描数据帧一次,每次扫描都会执行n个操作。它使用pandas
    apply
    函数,效率不高,但没有它我无法找到这样做的方法。我希望听到一个不使用apply进行过滤的解决方案

    我使用了一个小的可复制的例子,你可能需要调整索引以匹配你的代码。我相信这个例子更容易理解

    将熊猫作为pd导入
    将numpy作为np导入
    df=pd.DataFrame({“lat”:[22.5,10.76,7.341,22.5],“log”:[3.64,7.234,135,3.644],“level”:[2,8,19,9])
    lat1=np.数组([22.51,7.33])
    lon1=np.数组([3.6135.02])
    
    接下来的几行创建了一个元组列表,每个元组由一个
    pandas.Interval
    对象组成。这里的元组表示(lat1[i]+-x,lon1[i]+-x)。注意,我不必使用pandas.Interval-我可以构建另一个元组(lat1[i]-x,lat1[i]+x)。但我决定使用pandas Interval,这并不重要。 结果:对于每对[lat1,lon1],我们有一个两个熊猫间隔的元组,每个间隔为+-0.125

    interval\u list=[]
    常数加总=0.125
    对于i,枚举中的项目(lat1):
    间隔列表追加((pd.interval(左=lat1[i]-const\u add,右=lat1[i]+const\u add),pd.interval(左=lon1[i]-const\u add,右=lon1[i]+const\u add)))
    
    现在我们要过滤数据帧。为了使用
    apply
    ,我创建了一个自定义函数:它检查当前行是否在元组中,如果在元组中,它将返回lat1数组中的索引(稍后您将看到它为什么有用)

    def在范围内(行、间隔列表):
    对于i,枚举中的项(间隔列表):
    如果项目[0]中的行[0]和项目[1]中的行[1]:
    返回i
    返回np.nan
    df[“点”]=df.apply(λx:在_范围内(x,间隔列表),轴=1)
    
    在代码的这一点上,我们有一个列名“point”。它的值如下:如果行靠近点i(其中i是lat1[i]和lon1[i]中的索引),则值为i。如果没有闭合点,则值为nan

    现在剩下的就是找到每个点的最大值和最小值,这可以使用
    groupby
    轻松实现:

    max_series=df.groupby(by=“point”)[“level”].max()
    min_series=df.groupby(by=“point”)[“level”].min()
    
    您有两个系列,其中索引与lat1和lon[1]中的索引相同。您可以使用
    series.array
    轻松地将它们转换为数组。 值得一提的是,您没有说明如何处理缺少的值-如果df中没有点接近点(lat1[50],lon1[50]),那么最大和最小数组中的值是多少?这就是为什么我将其保留为一个系列,我相信在将其更改为数组之前更容易操作它

    将整个代码放在一起:

    将熊猫作为pd导入
    将numpy作为np导入
    df=pd.DataFrame({“lat”:[22.5,10.76,7.341,22.5],“log”:[3.64,7.234,135,3.644],“level”:[2,8,19,9])
    lat1=np.数组([22.51,7.33])
    lon1=np.数组([3.6135.02])
    间隔列表=[]
    常数加总=0.125
    对于i,枚举中的项目(lat1):
    间隔列表追加((pd.interval(左=lat1[i]-const\u add,右=lat1[i]+const\u add),pd.interval(左=lon1[i]-const\u add,右=lon1[i]+const\u add)))
    def在_范围内(行、间隔列表):
    对于i,枚举中的项(间隔列表):
    如果项目[0]中的行[0]和项目[1]中的行[1]:
    返回i
    返回np.nan
    df[“点”]=df.apply(λx:在_范围内(x,间隔列表),轴=1)
    max_arr=df.groupby(by=“point”)[“level”].max()
    min_arr=df.groupby(by=“point”)[“level”].min()
    #或:
    #max_arr=df.groupby(by=“point”)[“level”].max()数组
    
    我使用回答中描述的技巧有效地获得1D中对应于bin的索引,然后在
    lon
    lat
    的组上循环以获得两者的交点。 我在这里使用
    numpy
    ,不直接应用
    min/max
    ,而是关注索引

    import numpy as np
    from scipy.sparse import csr_matrix
    
    def digitize_group(x, bins):
        idx_x = np.digitize(x, bins)
        n, m = len(x), len(bins) + 1
        s = csr_matrix((np.arange(n), [idx_x, np.arange(n)]), shape=(m, n))
        return [group for group in np.split(s.data, s.indptr[1:-1])]
    
    # Create dummy data
    n = 100000  # 17671817
    step = 0.25  # Note the shift by step/2 to transform your arrays to bins
    bins_lon = np.arange(-11-step/2, 47+step/2, step) 
    bins_lat = np.arange(71.5+step/2, 34.5-step/2, -step)
    lon = np.random.uniform(low=bins_lon.min(), high=bins_lon.max(), size=n)
    lat = np.random.uniform(low=bins_lat.min(), high=bins_lat.max(), size=n)
    
    # Get the 1D groups
    group_lon = digitize_group(lon, bins_lon)
    group_lat = digitize_group(lat, bins_lat)
    
    # Combine to 2D groups
    group_lonlat = np.zeros((len(group_lon), len(group_lat)), dtype=object)
    for i, lo in enumerate(group_lon):
        for j, la in enumerate(group_lat):
            group_lonlat[i, j] = np.intersect1d(lo, la, assume_unique=True)
    
    print(group_lonlat[13, 17])
    # array([   15606,   131039,   168479,   171734,   174281,   266717,   ....
    
    通过访问
    grouplonlat[i,j]
    您可以得到一个索引列表
    K<
    
    import numpy as np
    from scipy.sparse import csr_matrix
    
    def digitize_group(x, bins):
        idx_x = np.digitize(x, bins)
        n, m = len(x), len(bins) + 1
        s = csr_matrix((np.arange(n), [idx_x, np.arange(n)]), shape=(m, n))
        return [group for group in np.split(s.data, s.indptr[1:-1])]
    
    # Create dummy data
    n = 100000  # 17671817
    step = 0.25  # Note the shift by step/2 to transform your arrays to bins
    bins_lon = np.arange(-11-step/2, 47+step/2, step) 
    bins_lat = np.arange(71.5+step/2, 34.5-step/2, -step)
    lon = np.random.uniform(low=bins_lon.min(), high=bins_lon.max(), size=n)
    lat = np.random.uniform(low=bins_lat.min(), high=bins_lat.max(), size=n)
    
    # Get the 1D groups
    group_lon = digitize_group(lon, bins_lon)
    group_lat = digitize_group(lat, bins_lat)
    
    # Combine to 2D groups
    group_lonlat = np.zeros((len(group_lon), len(group_lat)), dtype=object)
    for i, lo in enumerate(group_lon):
        for j, la in enumerate(group_lat):
            group_lonlat[i, j] = np.intersect1d(lo, la, assume_unique=True)
    
    print(group_lonlat[13, 17])
    # array([   15606,   131039,   168479,   171734,   174281,   266717,   ....
    
    bins_lon[i] < lon[k] < bins_lon[i+1] & bins_lat[j] < lat[k] < bins_lat[j+1]
    
    import sortednp as snp
    for i in range(len(group_lon)):
        for j in range(len(group_lat)):
            group_lonlat[i, j], (ii, jj) = snp.intersect(group_lon[i], group_lat[j], 
                                                         indices=True)
            group_lon[i] = np.delete(group_lon[i], ii)
            group_lat[j] = np.delete(group_lat[j], jj)