用矢量化方法替换python for循环以丢弃丢失的数据

Posted

技术标签:

【中文标题】用矢量化方法替换python for循环以丢弃丢失的数据【英文标题】:Replace python for loops with vectorized method to discard missing data 【发布时间】:2016-05-06 09:56:03 【问题描述】:

我正在为数据清理操作而苦苦挣扎。我有一个包含 id、投资组合月份 (port_months) 和投资组合编号 (port) 的大型数据框,例如:

                id          port      port_months backtest_month
49025        USA0EBZ0         0            1             1
80689        USA0EBZ0         0            2             2
224952       USA0EBZ0         0            3             4
  ...           ...          ...          ... 
227370       USA03BE0         1            1             12
229804       USA03BE0         1            2             13
232262       USA03BE0         1            3             14
  ...           ...          ...          ...

很遗憾,我经常遇到新的id进入系统,数据不完整的情况,例如:

                id          port      port_months backtest_month
63682        USA06W90         5            7           66
236452       USA06W90         5            8           67
238905       USA06W90         5            9           68
241358       USA06W90         5           10           69
243808       USA06W90         5           11           70
246229       USA06W90         5           12           71

这里的问题是这个id的数据进入了port_months = 7的数据框,而不是port_months = 1。我需要删除所有这些不完整的数据,因为另一个函数需要作用于只包含完整数据的数据集。所以,在这个例子中,我需要删除这个id,USA06W90,port = 5的数据(虽然你在这里看不到,但是port = 6的数据是完整的等等)。

我已经编写了一个简单的循环,它可以执行我想要的操作,但是速度非常慢,而且我确信我可以使用矢量化来做一些更复杂的事情:

for id in df.id:
    for port in df.port.unique(): #so loop over ports where the current stock has some data, not those for which it is absent from the system
        first_df = df[(df.id == id) & (df.port == port) & (df.port_months == 1)] #get the 1st row from the current port's dataframe
        if first_df.empty:
            df.drop(df[(df.id == id) & (df.port == port)].index, inplace = True) # drop all the rows associated with current id and port (i.e. all port_months for current port and id)

目前这需要 30 多分钟才能执行!

我一直在想一些巧妙的使用方法

groupby('id', port).apply(lambda x: x.port = x[x.port_months == 1].port)

或者什么,或者试图以某种方式使用一些技巧来构建新的投资组合并做ffill

port_new = df[df.port_months == 1].groupby('id', as_index = False).apply(lambda x: x.backtest_month / 12 )

重置索引,然后通过合并索引与df重新组合

这给出了:

                id          port      port_months backtest_month
49025        USA0EBZ0         0            1             1
80689        USA0EBZ0         NaN          2             2
224952       USA0EBZ0         NaN          3             4
  ...           ...          ...          ... 
227370       USA03BE0         1            1             12
229804       USA03BE0         NaN          2             13
232262       USA03BE0         NaN          3             14
  ...           ...          ...          ...

然后可以使用

填充 nans
df.fillna['port_new'](method = 'ffill')

这几乎可以工作,并且速度很快,但问题是您会遇到 id 进入然后再次离开数据集的情况,因此 ffill 也会填充所有这些 nas,而不是删除行,例如下面的 Nans 将被 5s 填充。

例如

                id          port      port_months backtest_month
63682        USA06W90         5            11           70
236452       USA06W90         5            12           71
238905       USA06W90       NaN             1           121
241358       USA06W90       NaN             2           122
243808       USA06W90       NaN             3           123
246229       USA06W90       NaN             4           124

【问题讨论】:

【参考方案1】:

要生成唯一的投资组合,您似乎需要创建一个由idport 组成的密钥。然后,您可以使用.loc 进行高效过滤,如下所示:

df = pd.DataFrame('backtest_month': [70, 71, 121, 122, 123],
                   'id': ['USA06W90', 'USA06W90', 'USA06W90', 'USA06W90', 'USA06W90'],
                   'port': [5, 5, 1, 1, 1],
                   'port_months': [11, 12, 1, 2, 3])

>>> df
              id  port  port_months  backtest_month         key
63682   USA06W90     5           11              70  USA06W90_5
236452  USA06W90     5           12              71  USA06W90_5
238905  USA06W90     1            1             121  USA06W90_1
241358  USA06W90     1            2             122  USA06W90_1
243808  USA06W90     1            3             123  USA06W90_1

#  Create a unique portfolio identifier.
df['key'] = df['id'] + '_' + df.port.astype(str)

# Use .loc to locate all unique portfolios that had a `port_months` value of one.
portfolios_first_month = df.loc[df.port_months == 1, 'key'].unique()
>>> portfolios_first_month
array(['USA06W90_1'], dtype=object)

# Use .loc again to locate all portfolio keys that were previously identified above.  
# The colon indicates that all columns should be returned.
df_filtered = df.loc[df.key.isin(portfolios_first_month), :]

>>> df_filtered
              id  port  port_months  backtest_month         key
238905  USA06W90     1            1             121  USA06W90_1
241358  USA06W90     1            2             122  USA06W90_1
243808  USA06W90     1            3             123  USA06W90_1

它生成一个包含所有唯一键的数组,其中 port_months 的值为 1(即没有丢失数据)。

df.loc[df.key.isin(portfolios_first_month), :] 然后定位所有这些键值并返回数据框中的所有列。

【讨论】:

谢谢。因此,您根据密钥必须存在于 port_months == 1 (df.key.isin(portfolios_first_month) 的基础上进行过滤。如果不是,那么与 id 及其端口相关联的特定密钥将被过滤出。聪明!大概可以用 groupby 做类似的事情? 但是我并不完全理解代码。 df.loc[df.port_months == 1, 'key'].unique()。因此,这将创建一个数组,其中每个键的 port_months == 1 的 loc。而 df.key.isin(portfolios_first_month), : 检查当前键是否有一个条目。我不熟悉这种过滤。 df['key'] = [row['id'] + '' + str(row['port']) for _, row in df.iterrows()] this行似乎会导致内存峰值(第一次杀死内核并且非常慢!需要 10 - 20 秒)。为什么不直接做 df['key'] = df['id'] + '' + str(df['port'])?那肯定会快得多。 我不确定 groupby 会在没有密钥的情况下为您提供什么。我可以获得idport 值,但单独使用这些值并不是很有用。我添加了一种更有效的方法来创建密钥。如果你愿意,你现在可以gb = df.groupby(portfolios_first_month) 谢谢,太好了。我不太明白你会如何使用 gb = df.groupby(portfolios_first_month)

以上是关于用矢量化方法替换python for循环以丢弃丢失的数据的主要内容,如果未能解决你的问题,请参考以下文章

python——用lambda函数替换for循环

当步长大于1时,通过数组切片和numpy.diff替换python中的for循环

在python中为依赖于索引的函数向量化嵌套的for循环

解释numpy向量化函数应用VS python for循环的速度差异

Python:替换多个 for 循环、多个迭代器

queryList 一次抓取多个网页内容的方法--目前只有用循环 替换页码或者给出url循环进行 queryList没有像python一样的yied迭代方法 queryList 实现多个实例抓取