【问题标题】:Select rows from a pandas dataframe with a numpy 2D array on multiple columns从 pandas 数据框中选择行,在多列上有一个 numpy 2D 数组
【发布时间】:2017-09-11 17:17:15
【问题描述】:

数据

我有一个包含 5 列的数据框:

  • 原点经纬度(origin_latorigin_lng
  • 目的地的经纬度(dest_lat, dest_lng
  • 从其他字段计算的分数

我有一个矩阵M,其中包含成对的起点和终点纬度/经度。其中一些对存在于数据框中,其他对不存在。

目标

我的目标有两个:

  1. M 中选择数据框前四列中不存在的所有对,将函数func 应用于它们(以计算得分列),并将结果附加到现有数据框。 注意:我们不应该为已经存在的行重新计算分数。
  2. 添加缺失的行后,在新的数据框dfs 中选择选择矩阵M 定义的所有行。

示例代码

# STEP 1: Generate example data
ctr_lat = 40.676762
ctr_lng = -73.926420
N = 12
N2 = 3

data = np.array([ctr_lat+np.random.random((N))/10,
                 ctr_lng+np.random.random((N))/10,
                 ctr_lat+np.random.random((N))/10,
                 ctr_lng+np.random.random((N))/10]).transpose()

# Example function - does not matter what it does
def func(x):
    return np.random.random()

# Create dataframe
geocols = ['origin_lat','origin_lng','dest_lat','dest_lng']
df = pd.DataFrame(data,columns=geocols)
df['score'] = df.apply(func,axis=1)

这给了我一个像这样的数据框df

    origin_lat  origin_lng   dest_lat   dest_lng     score
0    40.684887  -73.924921  40.758641 -73.847438  0.820080
1    40.703129  -73.885330  40.774341 -73.881671  0.104320
2    40.761998  -73.898955  40.767681 -73.865001  0.564296
3    40.736863  -73.859832  40.681693 -73.907879  0.605974
4    40.761298  -73.853480  40.696195 -73.846205  0.779520
5    40.712225  -73.892623  40.722372 -73.868877  0.628447
6    40.683086  -73.846077  40.730014 -73.900831  0.320041
7    40.726003  -73.909059  40.760083 -73.829180  0.903317
8    40.748258  -73.839682  40.713100 -73.834253  0.457138
9    40.761590  -73.923624  40.746552 -73.870352  0.867617
10   40.748064  -73.913599  40.746997 -73.894851  0.836674
11   40.771164  -73.855319  40.703426 -73.829990  0.010908

然后我可以人为地创建选择矩阵M,其中包含数据框中存在的 3 行和不存在的 3 行。

# STEP 2: Generate data to select
# As an example, I select 3 rows that are part of the dataframe, and 3 that are not
data2 = np.array([ctr_lat+np.random.random((N2))/10,
                  ctr_lng+np.random.random((N2))/10,
                  ctr_lat+np.random.random((N2))/10,
                  ctr_lng+np.random.random((N2))/10]).transpose()

M = np.concatenate((data[4:7,:],data2))

矩阵M 如下所示:

array([[ 40.7612977 , -73.85348031,  40.69619549, -73.84620489],
       [ 40.71222463, -73.8926234 ,  40.72237185, -73.86887696],
       [ 40.68308567, -73.84607722,  40.73001434, -73.90083107],
       [ 40.7588412 , -73.87128079,  40.76750639, -73.91945371],
       [ 40.74686156, -73.84804047,  40.72378653, -73.92207075],
       [ 40.6922673 , -73.88275402,  40.69708748, -73.87905543]])

从这里,我不知道如何知道M 中的哪些行在df 中不存在并添加它们。我也不知道如何从df 中选择M 中的所有行。

想法

我的想法是识别缺失的行,将它们附加到 df 并带有 nan 分数,然后仅重新计算 nan 行的分数。但是,我不知道如何在不循环矩阵M 的每个元素的情况下有效地选择这些行。

有什么建议吗? 非常感谢您的帮助!

【问题讨论】:

    标签: python pandas numpy select dataframe


    【解决方案1】:

    有什么理由不使用merge

    df2 = pd.DataFrame(M, columns=geocols) 
    df = df.merge(df2, how='outer')
    ix = df.score.isnull()
    df.loc[ix, 'score'] = df.loc[ix].apply(func, axis=1)
    

    它完全按照您的建议进行:将缺失的行 df 与 nan 分数相加,识别 nans,计算这些行的分数。

    【讨论】:

    • 您会在末尾使用inner 连接来选择矩阵M 中的所有行吗?
    • 没有。内连接将为您提供两个表中都存在的值。这将使df 成为操作之前的子集。如果您需要知道 df 中存在哪些行而 M 中不存在哪些行,您可以将这 4 列转换为 Index 并使用 difference() 运算符
    【解决方案2】:

    所以这个解决方案确实循环了 M 中的每一行,但不是每个元素。步骤是:

    1. 遍历 M 中的每一行并确定它是否在 df 中。如果它在其中,请保存索引。如果不是,请计算分数并保存。
    2. 通过从上面获取新的 M 行并附加在 df 中找到的行来创建 M 数据帧。
    3. 只需附加 M 的新行,即可创建新版本的数据框。

    希望这会有所帮助 - 我意识到它仍然有一个循环,但我还没有想出如何摆脱它。您的问题还仅说明 df 可能很大,并且您希望避免循环 M 的元素,这至少可以通过仅循环行来避免。


    M_in_df = []
    M_not_in_df = []
    
    for m in M:
        df_index = (df.iloc[:,:4].values == m).all(axis=1)
        if df_index.any():
            M_in_df.append(np.argmax(df_index))
        else:
            M_not_in_df.append(np.append(m, func(m)))    
    
    M_df = pd.DataFrame(M_not_in_df, columns=df.columns).append(df.iloc[M_in_df], ignore_index=True)
    
    new_df = df.append(pd.DataFrame(M_not_in_df, columns=df.columns), ignore_index=True)
    

    【讨论】:

      【解决方案3】:

      M 转换为DataFrame,与df 连接:

      df2 = pd.DataFrame(M, columns=geocols)
      df3 = pd.concat([df, df2], ignore_index=True)
      

      仅根据geocols 中的列删除重复行:

      df3 = df3.drop_duplicates(subset=geocols)
      

      使用NaNscore 获取行掩码:

      m = df3.score.isnull()
      

      将分数应用于被屏蔽的行,并存储在df3

      df3.loc[m, 'score'] = df3[m].apply(func, axis=1)
      

      你会得到一个 SettingWithCopyWarning,但它有效。

      【讨论】:

        【解决方案4】:

        您正在进行地理空间分析,我认为结合一些标准方法非常重要。也就是说,您的每一行/条目都由一对坐标标识,因此,将它们转换为 WKT 会很有意义。

        使用WKT,你只需要检查新数据的WKT是否已经在旧数据中找到:

        # from shapely.wkt import dumps
        # import shapely.geometry as sg
        
        In [27]: M = np.array([[ 40.761998, -73.898955, 40.767681, -73.865001],
            ...:               [ 40.736863, -73.859832, 40.681693, -73.907879],
            ...:               [ 40.761298, -73.853480, 40.696195, -73.846205],
            ...:               [ 40.7588412 , -73.87128079,  40.76750639, -73.91945371],
            ...:               [ 40.74686156, -73.84804047,  40.72378653, -73.92207075],
            ...:               [ 40.6922673 , -73.88275402,  40.69708748, -73.87905543]])
        In [28]: df
        Out[28]: 
            origin_lat  origin_lng   dest_lat   dest_lng     score  
        0    40.684887  -73.924921  40.758641 -73.847438  0.820080   
        1    40.703129  -73.885330  40.774341 -73.881671  0.104320   
        2    40.761998  -73.898955  40.767681 -73.865001  0.564296   
        3    40.736863  -73.859832  40.681693 -73.907879  0.605974   
        4    40.761298  -73.853480  40.696195 -73.846205  0.779520   
        5    40.712225  -73.892623  40.722372 -73.868877  0.628447   
        6    40.683086  -73.846077  40.730014 -73.900831  0.320041   
        7    40.726003  -73.909059  40.760083 -73.829180  0.903317   
        8    40.748258  -73.839682  40.713100 -73.834253  0.457138   
        9    40.761590  -73.923624  40.746552 -73.870352  0.867617   
        10   40.748064  -73.913599  40.746997 -73.894851  0.836674   
        11   40.771164  -73.855319  40.703426 -73.829990  0.010908   
        
        # Generate WKT for the original dataframe
        In [29]: df['wkt'] = df.apply(lambda x: dumps(sg.LineString([x[:2], x[2:4]]),
                                                      rounding_precision=6),
                                      axis=1)
        
        In [29]: df
        Out[29]: 
            origin_lat  origin_lng   dest_lat   dest_lng     score                                                 wkt
        0    40.684887  -73.924921  40.758641 -73.847438  0.820080   LINESTRING (40.684887 -73.924921, 40.758641 -7...
        1    40.703129  -73.885330  40.774341 -73.881671  0.104320   LINESTRING (40.703129 -73.885330, 40.774341 -7...
        2    40.761998  -73.898955  40.767681 -73.865001  0.564296   LINESTRING (40.761998 -73.898955, 40.767681 -7...
        3    40.736863  -73.859832  40.681693 -73.907879  0.605974   LINESTRING (40.736863 -73.859832, 40.681693 -7...
        4    40.761298  -73.853480  40.696195 -73.846205  0.779520   LINESTRING (40.761298 -73.853480, 40.696195 -7...
        5    40.712225  -73.892623  40.722372 -73.868877  0.628447   LINESTRING (40.712225 -73.892623, 40.722372 -7...
        6    40.683086  -73.846077  40.730014 -73.900831  0.320041   LINESTRING (40.683086 -73.846077, 40.730014 -7...
        7    40.726003  -73.909059  40.760083 -73.829180  0.903317   LINESTRING (40.726003 -73.909059, 40.760083 -7...
        8    40.748258  -73.839682  40.713100 -73.834253  0.457138   LINESTRING (40.748258 -73.839682, 40.713100 -7...
        9    40.761590  -73.923624  40.746552 -73.870352  0.867617   LINESTRING (40.761590 -73.923624, 40.746552 -7...
        10   40.748064  -73.913599  40.746997 -73.894851  0.836674   LINESTRING (40.748064 -73.913599, 40.746997 -7...
        11   40.771164  -73.855319  40.703426 -73.829990  0.010908   LINESTRING (40.771164 -73.855319, 40.703426 -7...
        
        # Generate WKT for the new data
        In [30]: new_wkt = [dumps(sg.LineString(r.reshape(2,2)), 
                                  rounding_precision=6)
                            for r in M]
        In [30]: np.isin(new_wkt, df.wkt)
        Out[30]: array([ True,  True,  True, False, False, False], dtype=bool)
        
        # Only put the WKT not found in the original dataframe into the a new dataframe
        In [31]: df2 = pd.DataFrame(M[np.isin(new_wkt, df.wkt)], columns=['origin_lat', 'origin_lng', 'dest_lat', 'dest_lng'])
        In [32]: df2['wkt'] = np.array(new_wkt)[np.isin(new_wkt, df.wkt)]
        
        # Only do calculation for the new entries
        In [33]: df2['score'] = 0  # or do whatever score calculation needed
        
        # Combine the new to the old
        In [34]: df.append(df2)
        Out[34]: 
             dest_lat   dest_lng  origin_lat  origin_lng     score                                                wkt
        0   40.758641 -73.847438   40.684887  -73.924921  0.820080  LINESTRING (40.684887 -73.924921, 40.758641 -7...
        1   40.774341 -73.881671   40.703129  -73.885330  0.104320  LINESTRING (40.703129 -73.885330, 40.774341 -7...
        2   40.767681 -73.865001   40.761998  -73.898955  0.564296  LINESTRING (40.761998 -73.898955, 40.767681 -7...
        3   40.681693 -73.907879   40.736863  -73.859832  0.605974  LINESTRING (40.736863 -73.859832, 40.681693 -7...
        4   40.696195 -73.846205   40.761298  -73.853480  0.779520  LINESTRING (40.761298 -73.853480, 40.696195 -7...
        5   40.722372 -73.868877   40.712225  -73.892623  0.628447  LINESTRING (40.712225 -73.892623, 40.722372 -7...
        6   40.730014 -73.900831   40.683086  -73.846077  0.320041  LINESTRING (40.683086 -73.846077, 40.730014 -7...
        7   40.760083 -73.829180   40.726003  -73.909059  0.903317  LINESTRING (40.726003 -73.909059, 40.760083 -7...
        8   40.713100 -73.834253   40.748258  -73.839682  0.457138  LINESTRING (40.748258 -73.839682, 40.713100 -7...
        9   40.746552 -73.870352   40.761590  -73.923624  0.867617  LINESTRING (40.761590 -73.923624, 40.746552 -7...
        10  40.746997 -73.894851   40.748064  -73.913599  0.836674  LINESTRING (40.748064 -73.913599, 40.746997 -7...
        11  40.703426 -73.829990   40.771164  -73.855319  0.010908  LINESTRING (40.771164 -73.855319, 40.703426 -7...
        0   40.767681 -73.865001   40.761998  -73.898955  0.000000  LINESTRING (40.761998 -73.898955, 40.767681 -7...
        1   40.681693 -73.907879   40.736863  -73.859832  0.000000  LINESTRING (40.736863 -73.859832, 40.681693 -7...
        2   40.696195 -73.846205   40.761298  -73.853480  0.000000  LINESTRING (40.761298 -73.853480, 40.696195 -7...
        

        其他 cmets:

        1. 使用以 WKT/WKB 编码的地理空间信息,可以轻松使用可用的地理空间库来计算 score 列(如果涉及此类计算)
        2. 为 WKT 设置正确的精度通常是地理空间数据的必要考虑因素(这里我将其设置为 precision 到 6)
        3. 性能。 df(或df.append(df2) 部分)的维度在每次从新数据框中添加新行时都会发生变化,在这种情况下为df2。从本质上讲,这意味着如果此类“更新”频繁发生,性能将会飙升。
        4. 如果分析是围绕地理空间数据构建的,geopandas 可能值得研究。

        【讨论】:

          【解决方案5】:

          让我们首先将 M 塑造成一个名为 df_temp 的数据框:

          In [1]: df_temp=pd.DataFrame(M,columns=('origin_lat','origin_lng','dest_lat','dest_lng'))
          
          In [2]: df_temp
          Out[2]:
             origin_lat  origin_lng   dest_lat   dest_lng
          0   40.724872  -73.843830  40.768628 -73.875295
          1   40.744625  -73.858908  40.770675 -73.915897
          2   40.683664  -73.916877  40.700891 -73.904609
          3   40.774582  -73.871768  40.703176 -73.833921
          4   40.680940  -73.839505  40.752041 -73.882552
          5   40.677105  -73.897702  40.743859 -73.883683
          

          使用合并,我们现在可以轻松地跟踪 df 中 M 中的元素:

          In [3]: dfs = df.merge(df_temp,on=['origin_lat','origin_lng','dest_lat','dest_lng'],
          right_index=True)
          
          In [4]: dfs
          Out[4]:
             origin_lat  origin_lng   dest_lat   dest_lng     score
          4   40.724872  -73.843830  40.768628 -73.875295  0.705182
          5   40.744625  -73.858908  40.770675 -73.915897  0.724282
          6   40.683664  -73.916877  40.700891 -73.904609  0.645395
          

          注意:right_index参数可以让我们保持df的索引,这样我们就知道df的哪些行也在M中

          最后,我们可以在 df_temp 中添加 df 中没有的行:

          # Compute the scores of df_temp
          df_temp['score'] = [func(df_temp.iloc[i]) for i in range(len(df_temp))]
          # Append elements of df_temp to df
          df.append(df_temp,ignore_index=True)
          # Erase duplicates
          df.drop_duplicates(subset=['origin_lat','origin_lng','dest_lat','dest_lng'])
          

          注意: drop_duplicates 中的子集只是在这里,因为您的分数函数是非确定性的

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2016-06-10
            • 2019-08-14
            • 2012-07-02
            • 1970-01-01
            相关资源
            最近更新 更多