【问题标题】:Setting value to a copy of a slice of a DataFrame将值设置为 DataFrame 切片的副本
【发布时间】:2017-03-20 21:59:17
【问题描述】:

我正在设置以下与我的情况和数据相似的示例:

说,我有以下 DataFrame:

df = pd.DataFrame ({'ID' : [1,2,3,4],
             'price' : [25,30,34,40],
             'Category' : ['small', 'medium','medium','small']})


  Category  ID  price
0    small   1     25
1   medium   2     30
2   medium   3     34
3    small   4     40

现在,我有以下函数,它根据以下逻辑返回折扣金额:

def mapper(price, category):
    if category == 'small':
        discount = 0.1 * price
    else:
        discount = 0.2 * price
    return discount

现在我想要生成的 DataFrame:

  Category  ID  price Discount
0    small   1     25      0.25
1   medium   2     30      0.6
2   medium   3     40      0.8
3    small   4     40      0.4

所以我决定在价格列上调用 series.map,因为我不想使用 apply。我正在处理一个大型 DataFrame,并且 map 比应用快得多。

我试过这样做:

for c in list(sample.Category.unique()):
    sample[sample['Category'] == c]['Discount'] = sample[sample['Category'] == c]['price'].map(lambda x: mapper(x,c))

这并没有像我预期的那样工作,因为我试图在 DataFrame 切片的副本上设置一个值。

我的问题是, 有没有办法在不使用df.apply() 的情况下做到这一点?

【问题讨论】:

    标签: python pandas numpy dataframe


    【解决方案1】:

    np.where 的一种方法-

    mask = df.Category.values=='small'
    df['Discount'] = np.where(mask,df.price*0.01, df.price*0.02)
    

    另一种不同的方式 -

    df['Discount'] = df.price*0.01
    df['Discount'][df.Category.values!='small'] *= 2
    

    为了提高性能,您可能希望使用数组数据,因此我们可以在使用df.price 的地方使用df.price.values

    基准测试

    方法-

    def app1(df): # Proposed app#1 here
        mask = df.Category.values=='small'
        df_price = df.price.values
        df['Discount'] = np.where(mask,df_price*0.01, df_price*0.02)
        return df
    
    def app2(df): # Proposed app#2 here
        df['Discount'] = df.price.values*0.01
        df['Discount'][df.Category.values!='small'] *= 2
        return df
    
    def app3(df): # @piRSquared's soln
        df.assign(
        Discount=((1 - (df.Category.values == 'small')) + 1) / 100 * df.price.values)
        return df
    
    def app4(df): # @MaxU's soln
        df.assign(Discount=df.price * df.Category.map({'small':0.01}).fillna(0.02))
        return df
    

    时间安排 -

    1) 大型数据集:

    In [122]: df
    Out[122]: 
      Category  ID  price  Discount
    0    small   1     25      0.25
    1   medium   2     30      0.60
    2   medium   3     34      0.68
    3    small   4     40      0.40
    
    In [123]: df1 = pd.concat([df]*1000,axis=0)
         ...: df2 = pd.concat([df]*1000,axis=0)
         ...: df3 = pd.concat([df]*1000,axis=0)
         ...: df4 = pd.concat([df]*1000,axis=0)
         ...: 
    
    In [124]: %timeit app1(df1)
         ...: %timeit app2(df2)
         ...: %timeit app3(df3)
         ...: %timeit app4(df4)
         ...: 
    1000 loops, best of 3: 209 µs per loop
    10 loops, best of 3: 63.2 ms per loop
    1000 loops, best of 3: 351 µs per loop
    1000 loops, best of 3: 720 µs per loop
    

    2) 非常大的数据集:

    In [125]: df1 = pd.concat([df]*10000,axis=0)
         ...: df2 = pd.concat([df]*10000,axis=0)
         ...: df3 = pd.concat([df]*10000,axis=0)
         ...: df4 = pd.concat([df]*10000,axis=0)
         ...: 
    
    In [126]: %timeit app1(df1)
         ...: %timeit app2(df2)
         ...: %timeit app3(df3)
         ...: %timeit app4(df4)
         ...: 
    1000 loops, best of 3: 758 µs per loop
    1 loops, best of 3: 2.78 s per loop
    1000 loops, best of 3: 1.37 ms per loop
    100 loops, best of 3: 2.57 ms per loop
    

    通过数据重用进一步提升 -

    def app1_modified(df):
        mask = df.Category.values=='small'
        df_price = df.price.values*0.01
        df['Discount'] = np.where(mask,df_price, df_price*2)
        return df
    

    时间安排 -

    In [133]: df1 = pd.concat([df]*10000,axis=0)
         ...: df2 = pd.concat([df]*10000,axis=0)
         ...: df3 = pd.concat([df]*10000,axis=0)
         ...: df4 = pd.concat([df]*10000,axis=0)
         ...: 
    
    In [134]: %timeit app1(df1)
    1000 loops, best of 3: 699 µs per loop
    
    In [135]: %timeit app1_modified(df1)
    1000 loops, best of 3: 655 µs per loop
    

    【讨论】:

      【解决方案2】:

      还使用了一些numpy

      df.assign(
          Discount=((1 - (df.Category.values == 'small')) + 1) / 100 * df.price.values)
      
        Category  ID  price  Discount
      0    small   1     25      0.25
      1   medium   2     30      0.60
      2   medium   3     34      0.68
      3    small   4     40      0.40
      

      操作组件是

      (1 - (df.Category.values == 'small')) + 1) / 100 * df.price.values
      

      这会生成一个布尔数组并对其执行简单的算术运算以获得.01.02


      对给定数据的简单时间测试


      感谢@Divakar 指出这一点 对于使用 python 2.x 的用户,您需要使用 this 强制浮动问题。

      df.assign(
          Discount=((1 - (df.Category.values == 'small')) + 1) / 100. * df.price.values)
      

      【讨论】:

      • 我需要 100.0 或 1.0 才能在我的 Python 2.x 上转换为浮点值。
      • @Divakar 很好的评论。适合 2.x 用户。
      • 对 pandas 了解不多并好奇 - df.assign 是否比 df['column_name']= 性能更好?
      • @Divakar 没有。 pd.DataFrame.assign 很方便,因为它会生成 df 的副本,其中包含在关键字参数中指定的新列。基准测试很好,因为它不会破坏原始版本。也适用于管道内衬。我在您的示例中使用了它,以便我们进行苹果与苹果之间的比较,而无需每次迭代都重新创建 df
      【解决方案3】:

      这是另一种 Pandas 方法:

      In [67]: df.assign(Discount=df.price * df.Category.map({'small':0.01}).fillna(0.02))
      Out[67]:
        Category  ID  price  Discount
      0    small   1     25      0.25
      1   medium   2     30      0.60
      2   medium   3     34      0.68
      3    small   4     40      0.40
      

      【讨论】:

        猜你喜欢
        • 2015-10-06
        • 2016-09-23
        • 2016-02-17
        • 2016-08-24
        • 2016-06-16
        • 2021-08-09
        • 2016-07-27
        • 2018-06-18
        相关资源
        最近更新 更多