用Python自动化计算函数填充

4
我已经写好了下面的代码,并且它能够很好地工作并产生应有的结果:如果没有给出,则使用计算<前一个c * b>填充。问题是,我必须将其应用于一个更大的数据集len(df.index) = ca. 10,000,因此我目前的函数不合适,因为我需要写几千次:df['c'] = df.apply(func, axis =1)。针对这个数据集大小,pandas不支持使用while循环。有什么建议吗?
import pandas as pd
import numpy as np
import datetime

randn = np.random.randn
rng = pd.date_range('1/1/2011', periods=10, freq='D')

df = pd.DataFrame({'a': [None] * 10, 'b': [2, 3, 10, 3, 5, 8, 4, 1, 2, 6]},index=rng)
df["c"] =np.NaN

df["c"][0] = 1
df["c"][2] = 3


def func(x):
    if pd.notnull(x['c']):
        return x['c']
    else:
        return df.iloc[df.index.get_loc(x.name) - 1]['c'] * x['b']

df['c'] = df.apply(func, axis =1)
df['c'] = df.apply(func, axis =1)
df['c'] = df.apply(func, axis =1)
df['c'] = df.apply(func, axis =1)
df['c'] = df.apply(func, axis =1)
df['c'] = df.apply(func, axis =1)
df['c'] = df.apply(func, axis =1)

我使用的解决方案在这里找到的:https://dev59.com/iovda4cB1Zd3GeqPbpws - sorownas
@boardrider,你认为这个问题有什么问题吗? - chrisaycock
@hb.klein,您的编辑并没有使这个问题更容易被搜索到。标签“python”和“pandas”已经在这篇文章中了,这就是我和其他人找到它的方式。此外,Stack Overflow已经将标签添加到每个页面标题中,因此Google会更好地处理它。 - chrisaycock
很难清楚地了解你的问题实际是什么。尝试重新表述为:我想要做A,我尝试过B、C和D。这里是一个最小化的代码E,它展示了我的问题,并且这里是我在输入F或G时得到的异常信息。 - boardrider
@boardrider 这个问题对我来说非常有意义。 - chrisaycock
你很厉害,@chrisaycock :-) - boardrider
3个回答

4

以下是解决递归问题的好方法。在下周发布的v0.16.2中将有相关文档。请参阅numba文档。

由于真正繁重的工作是在快速jit编译代码中完成的,因此这将非常高效。

import pandas as pd
import numpy as np
from numba import jit

rng = pd.date_range('1/1/2011', periods=10, freq='D')
df = pd.DataFrame({'a': np.nan * 10, 'b': [2, 3, 10, 3, 5, 8, 4, 1, 2, 6]},index=rng)
df.ix[0,"c"] = 1
df.ix[2,"c"] = 3

@jit
def ffill(arr_b, arr_c):

    n = len(arr_b)
    assert len(arr_b) == len(arr_c)
    result = arr_c.copy()

    for i in range(1,n):
        if not np.isnan(arr_c[i]):
            result[i] = arr_c[i]
        else:
            result[i] = result[i-1]*arr_b[i]

    return result

df['d'] = ffill(df.b.values, df.c.values)

             a   b   c      d
2011-01-01 NaN   2   1      1
2011-01-02 NaN   3 NaN      3
2011-01-03 NaN  10   3      3
2011-01-04 NaN   3 NaN      9
2011-01-05 NaN   5 NaN     45
2011-01-06 NaN   8 NaN    360
2011-01-07 NaN   4 NaN   1440
2011-01-08 NaN   1 NaN   1440
2011-01-09 NaN   2 NaN   2880
2011-01-10 NaN   6 NaN  17280

请问您能否看一下这个问题,它非常相似。我们只是找不到解决方案:https://dev59.com/S10a5IYBdhLWcg3wD1Lb - sorownas

4
如果您在for循环中打印df的值:
for i in range(7):
    df['c'] = df.apply(func, axis =1)
    print(df)

你可以追踪c列中值的来源:
               a   b      c
2011-01-01  None   2      1    1
2011-01-02  None   3      3    3*1
2011-01-03  None  10      3    1*3*1
2011-01-04  None   3      9    3*1*3*1
2011-01-05  None   5     45    5*3*1*3*1
2011-01-06  None   8    360    ...
2011-01-07  None   4   1440    ...
2011-01-08  None   1   1440    ...
2011-01-09  None   2   2880    ...
2011-01-10  None   6  17280    6*2*4*8*5*3*3

您可以清楚地看到,这些值来自累积乘积。每一行都是前一行的值乘以某个新数字得出的。这个新数字有时来自b,有时是1(当c不为NaN时)。所以,如果我们能创建一个列d,其中包含这些“新”数字,那么所需的值可以通过cumprod计算得出:
df['c'] = df['d'].cumprod() 

import pandas as pd
import numpy as np
import datetime

randn = np.random.randn

def setup_df():
    rng = pd.date_range('1/1/2011', periods=10, freq='D')
    df = pd.DataFrame({'a': [None] * 10, 'b': [2, 3, 10, 3, 5, 8, 4, 1, 2, 6]},
                      index=rng)
    df["c"] = np.NaN
    df.iloc[0, -1] = 1
    df.iloc[2, -1] = 3
    return df

df = setup_df()
df['d'] = df['b']
mask = pd.notnull(df['c'])
df.loc[mask, 'd'] = 1
df['c'] = df['d'].cumprod()
print(df)

产量
               a   b      c  d
2011-01-01  None   2      1  1
2011-01-02  None   3      3  3
2011-01-03  None  10      3  1
2011-01-04  None   3      9  3
2011-01-05  None   5     45  5
2011-01-06  None   8    360  8
2011-01-07  None   4   1440  4
2011-01-08  None   1   1440  1
2011-01-09  None   2   2880  2
2011-01-10  None   6  17280  6

我在这里保留了 d 列以帮助显示 c 值的来源。当然,您可以删除该列。
del df['d']

正如chrisaycock所指出的那样,你甚至可以不定义d列,而是使用

df['c'] = np.where(pd.notnull(df['c']), 1, df['b']).cumprod()

这非常好,应该被接受为答案。如果 OP 想要一行代码,那就是 np.where(pd.notnull(df.c), 1, df.b).cumprod() - chrisaycock
谢谢,这是一个更好的表达方式。 - unutbu
请您看一下这个问题,它非常相似。我们只是找不到解决方案:https://dev59.com/S10a5IYBdhLWcg3wD1Lb - sorownas

1
您可以像这样编写一个写循环:

for i in range(1, len(df)):
    if pd.isnull(df.c[i]):
        df.c[i] = df.c[i-1] * df.b[i]

如果你觉得这个过程太慢,你可以使用 Numba 进行 jit。然而,在我的系统上,你的示例 DataFrame 太小了,无法进行有意义的测试。

哈哈,看我的例子...已经完成了!注意,你不能直接按照你写的方式进行JIT编译。你不能使用像pd.isnull这样的东西,也不能使用索引操作符;你必须使用numpy数组(然后在最后包装它们)。 - Jeff
@Jeff 哦,这很有道理。我的jit测试结果比原始的Python代码还要慢,但我曾经认为这只是因为DataFrame太小了的问题。感谢你的解释。 - chrisaycock
是的,它可以工作,但它将在Python模式下运行(因此它实际上并没有转换为LLVM等);您可以强制使用nopython模式,例如“jit(nopython=True)”,以强制它在执行不合规操作时引发错误 :) - Jeff
@chrisaycock:您能否看一下这个问题。这是一个类似的问题:https://dev59.com/S10a5IYBdhLWcg3wD1Lb - sorownas

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