将值设置为 DataFrame 切片的副本

5
我将设置以下示例,它类似于我的情况和数据:
假设我有以下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比apply更快。

我尝试过这样做:

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() 来实现这个?

3个回答

8

使用 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

4

这里是另一种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

4

同时还需要使用一些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


在给定数据上进行简单时间测试

enter image description here


感谢@Divakar指出这一点 对于使用python 2.x的人,您需要通过使用以下内容来强制浮点问题。

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

1
我需要在Python 2.x中将数值转换为浮点数,因此需要100.0或1.0。 - Divakar
@Divakar 不错的评论。适合2.x版本的用户。 - piRSquared
不太了解pandas,有点好奇 - df.assigndf['column_name'] =更高效吗? - Divakar
@Divakar 不需要。pd.DataFrame.assign 的便利之处在于它会生成一个副本,其中包含关键字参数指定的新列。这对于基准测试很有用,因为它不会覆盖原始数据。对于流水线操作也非常实用。我在你的示例中使用它,以便我们可以进行苹果与苹果的比较,而无需在每次迭代中重新创建 df - piRSquared

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接