首页 > 解决方案 > 如何在保持形状和索引的同时获得(快速)DataFrame 的第一个非 Nan 每日值?

问题描述

我有以下pd.DataFrame

from datetime import datetime
df1 = pd.DataFrame(
    data=[[0, 0, 1], [0, 1, 1], [1, 1, 0], [1, 1, 0], [0, 0, 1], [0, 1, 1], [1, 1, 0], [1, 1, 0]],
    index=[
        datetime(2020, 1, 1, 1, 10), datetime(2020, 1, 1, 1, 15), datetime(2020, 1, 1, 1, 20), datetime(2020, 1, 1, 1, 25),
        datetime(2020, 1, 2, 1, 10), datetime(2020, 1, 2, 1, 15), datetime(2020, 1, 2, 1, 20), datetime(2020, 1, 2, 1, 25)
    ]
)

我想将其转换为以下形式:

df2 = pd.DataFrame(
    data=[[0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 0, 0], [0, 0, 1], [0, 1, 0], [1, 0, 0], [0, 0, 0]],
    index=[
        datetime(2020, 1, 1, 1, 10), datetime(2020, 1, 1, 1, 15), datetime(2020, 1, 1, 1, 20), datetime(2020, 1, 1, 1, 25),
        datetime(2020, 1, 2, 1, 10), datetime(2020, 1, 2, 1, 15), datetime(2020, 1, 2, 1, 20), datetime(2020, 1, 2, 1, 25)
    ]
)

我设法通过以下方式实现了这一目标:

df3 = pd.concat([df1[col].loc[df1[col].replace(0, np.nan).groupby(df1.index.date).idxmax()].dropna().reindex(df1.index) for col in df1.columns], axis=1).replace(np.nan, 0).astype(int)

这样df2.equals(df3)评估为真。

我的问题是我的方式对于一个大的来说很慢,pd.DataFrame我想知道如何让它变得更快?

标签: pythonpandasdataframe

解决方案


一种解决方案:

只需获取每行中的前 1 个值:

df1[df1.cumsum(axis=1)!=1] = 0

设置临时日期 col

df1["date"] = df1.index.date

将任何重复的行设置为 0

df1[df1.duplicated()] = 0

摆脱临时日期列

df1.drop("date", axis=1, inplace=True)

这将我电脑上的运行时间大致减半:

对于 100 个循环:

提问方式:7.292934599994624s

方法一:0.3330558000016026s

通过不制作临时日期列可能会进行一些优化,但我不确定如何执行此操作。希望有更多熊猫知识的人可以告诉我!

此代码还假设数据已经按日期排序

import pandas as pd
from datetime import datetime
import numpy as np
import timeit

n = 200

data = [[0, 0, 1], [0, 1, 1], [1, 1, 0], [1, 1, 0]]*n
index = [[datetime(y, 1, 1, 1, x) for x in [10, 15, 20, 25]] for y in range(2020, 2020+n)]
index = [item for sublist in index for item in sublist]

df1 = pd.DataFrame(
    data=data,
    index=index
)

def method1(df):
    return pd.concat([df[col].loc[df[col].replace(0, np.nan).groupby(df.index.date).idxmax()].dropna().reindex(df.index) for col in df.columns], axis=1).replace(np.nan, 0).astype(int)

def method2(df):
    df3 = df.copy()
    df3[df3.cumsum(axis=1)!=1] = 0
    df3["date"] = df3.index.date
    df3[df3.duplicated()] = 0
    df3.drop("date", axis=1, inplace=True)
    return df3

start = timeit.default_timer()
for i in range(100):
    new_df = method1(df1)
end = timeit.default_timer()
print(end-start)

start = timeit.default_timer()
for i in range(100):
    new_df = method2(df1)
end = timeit.default_timer()
print(end-start)

推荐阅读