Pandas 如何标记开头 (1) 和多个结尾(2 或 3)之间的行?

问题描述 投票:0回答:6

我有以下数据框:

import numpy as np
import pandas as pd

df = pd.DataFrame([])
df['Date'] = ['2020-01-01','2020-01-02','2020-01-03','2020-01-04','2020-01-05',
              '2020-01-06','2020-01-07','2020-01-08','2020-01-09','2020-01-10',
              '2020-01-11','2020-01-12','2020-01-13','2020-01-14','2020-01-15',
              '2020-01-16','2020-01-17','2020-01-18','2020-01-19','2020-01-20']
df['Machine'] = ['A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A']
df['Signal'] = [0,1,2,0,1,3,0,0,0,3,0,1,0,0,3,0,1,0,0,1]
df['Status'] =  0

以下函数为机器 A 生成“状态”列。在信号列中,1 打开机器(状态列 1),该值保持为 1,直到机器收到 2 或 3(这是开关机器的信号)状态变为 0(关闭),直到机器再次收到信号 1。

我已经使用以下函数解决了维持先前状态行值为 1 或 0 的问题:

def s_gen(dataset, Signal):
    _status = 0
    status0 = []
    for (i) in Signal:
        if _status == 0:
            if i == 1:
                _status = 1 
        elif _status == 1:
            if (i == 2 or i==3):
                _status = 0
        status0.append(_status)
        
    dataset['status0'] = status0

    return dataset['status0']

df['Status'] = s_gen(df,df['Signal'])
df.drop('status0',axis=1,inplace = True)
df

这会将新创建的列附加到数据框中。然而,我有一个更大的数据框,机器列中有许多不同的值(分组为系列;A、A、A、B、B、B 等),并且函数的结果不能重叠。使用 groupby 不起作用。因此,我认为下一步是将每个“状态”序列生成为单独的列表,并将它们连接起来,然后将整个系列作为更大的外循环的一部分附加到更大的数据帧中。

这是期望的结果:

df = pd.DataFrame([])
df['Date'] = ['2020-01-01','2020-01-02','2020-01-03','2020-01-04','2020-01-05',
              '2020-01-06','2020-01-07','2020-01-08','2020-01-09','2020-01-10',
              '2020-01-11','2020-01-12','2020-01-13','2020-01-14','2020-01-15',
              '2020-01-16','2020-01-17','2020-01-18','2020-01-19','2020-01-20',
              '2020-01-01','2020-01-02','2020-01-03','2020-01-04','2020-01-05',
              '2020-01-06','2020-01-07','2020-01-08','2020-01-09','2020-01-10',
              '2020-01-11','2020-01-12','2020-01-13','2020-01-14','2020-01-15',
              '2020-01-16','2020-01-17','2020-01-18','2020-01-19','2020-01-20']

df['Machine'] = ['A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A',
                'B','B','B','B','B','B','B','B','B','B','B','B','B','B','B','B','B','B','B','B',]
df['Signal'] = [0,1,2,0,1,3,0,0,0,3,0,1,0,0,3,0,1,0,0,1,0,1,2,0,1,3,0,0,0,3,0,1,0,0,3,0,1,0,0,1]
df['Status'] = [0,1,0,0,1,0,0,0,0,0,0,1,1,1,0,0,1,1,1,1,0,1,0,0,1,0,0,0,0,0,0,1,1,1,0,0,1,1,1,1]
df

我正在努力解决的是,如果该函数单独处理每台机器的数据然后将其附加到数据帧,则它必须循环遍历每台机器,然后连接生成的所有状态系列,然后将较大的系列附加到数据帧。

这是我迄今为止尝试过的:

dfList = df[df['Machine']]
dfListU = pd.DataFrame([])
dfListU = dfList['Machine'].unique()
dfListU.flatten()

def s_gen2(item, dataset, Signal):
   
    data = df[df.Machine==m]
    for m in dfListU:
        _status = 0
        status0 = []

        for (i) in Signal:
            if _status == 0:
                if i == 1:
                    _status = 1 
            elif _status == 1:
                if (i == 2 or i==3):
                    _status = 0
            #status0.append(_status)

        dataset['status0'] = status0

        return dataset['status0']
    for i in dfListU:
        df1 = pd.concat(i)
    status0.append(_status)
df['Status'] = s_gen(df,df['Signal'])
df.drop('status0',axis=1,inplace = True)
df

这会导致错误 - KeyError: "None of [Index(['A', 'A', 'A', 'A', 'A', 'A', 'A', 'A', 'A '、'A'、'A'、'A'、'A'、'A'、 'A', 'A', 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'B', 'B', 'B '、'B'、 'B','B','B','B','B','B','B','B','B','B','B','B'], dtype='object')] 在 [列]"

通过 dfListU(唯一机器列表)循环该函数然后连接结果是否更好?我尝试避免使用循环,但找不到任何其他方法来将先前的状态行与信号列中的同一行进行比较。

真诚感谢任何帮助。

python pandas dataframe loops signals
6个回答
6
投票

一个简单的方法是

map
已知状态,然后
groupby.ffill
它们:

df['Status'] = (df['Signal']
 .map({1:1, 2:0, 3:0})
 .groupby(df['Machine']).ffill()
 .fillna(0, downcast='infer')
 )

输出:

          Date Machine  Signal  Status
0   2020-01-01       A       0       0
1   2020-01-02       A       1       1
2   2020-01-03       A       2       0
3   2020-01-04       A       0       0
4   2020-01-05       A       1       1
5   2020-01-06       A       3       0
6   2020-01-07       A       0       0
7   2020-01-08       A       0       0
8   2020-01-09       A       0       0
9   2020-01-10       A       3       0
10  2020-01-11       A       0       0
11  2020-01-12       A       1       1
12  2020-01-13       A       0       1
13  2020-01-14       A       0       1
14  2020-01-15       A       3       0
15  2020-01-16       A       0       0
16  2020-01-17       A       1       1
17  2020-01-18       A       0       1
18  2020-01-19       A       0       1
19  2020-01-20       A       1       1
20  2020-01-01       B       0       0
21  2020-01-02       B       1       1
22  2020-01-03       B       2       0
23  2020-01-04       B       0       0
24  2020-01-05       B       1       1
25  2020-01-06       B       3       0
26  2020-01-07       B       0       0
27  2020-01-08       B       0       0
28  2020-01-09       B       0       0
29  2020-01-10       B       3       0
30  2020-01-11       B       0       0
31  2020-01-12       B       1       1
32  2020-01-13       B       0       1
33  2020-01-14       B       0       1
34  2020-01-15       B       3       0
35  2020-01-16       B       0       0
36  2020-01-17       B       1       1
37  2020-01-18       B       0       1
38  2020-01-19       B       0       1
39  2020-01-20       B       1       1

6
投票

有更好的方法。使用 cumsum 函数标记以 2/3 条件开头的不同行/块集。然后屏蔽不为 1 的信号值,然后按机器和块对屏蔽列进行分组,并向前填充值。

m1 = df['Signal'].ne(1)
m2 = df['Signal'].isin([2, 3])

df['Status'] = df['Signal'].mask(m1).groupby([df['Machine'], m2.cumsum()]).ffill().fillna(0)

          Date Machine  Signal  Status
0   2020-01-01       A       0     0.0
1   2020-01-02       A       1     1.0
2   2020-01-03       A       2     0.0
3   2020-01-04       A       0     0.0
4   2020-01-05       A       1     1.0
5   2020-01-06       A       3     0.0
6   2020-01-07       A       0     0.0
7   2020-01-08       A       0     0.0
8   2020-01-09       A       0     0.0
9   2020-01-10       A       3     0.0
10  2020-01-11       A       0     0.0
11  2020-01-12       A       1     1.0
12  2020-01-13       A       0     1.0
13  2020-01-14       A       0     1.0
14  2020-01-15       A       3     0.0
15  2020-01-16       A       0     0.0
16  2020-01-17       A       1     1.0
17  2020-01-18       A       0     1.0
18  2020-01-19       A       0     1.0
19  2020-01-20       A       1     1.0

3
投票

您可以使用

np.select
作为状态机并使用矢量化代码:

import numpy as np

conds = [df['Signal'].eq(1), df['Signal'].isin([2, 3])]
vals = [1, 0]

# np.nan for df['Signal'] == 0 (default parameter)
status = pd.Series(np.select(condlist=conds, choicelist=vals, default=np.nan))

# fill forward the current status then fill remain values with 0
df['Status'] = status.groupby(df['Machine']).ffill().fillna(0).astype(int)

输出:

>>> df
          Date Machine  Signal  Status
0   2020-01-01       A       0       0
1   2020-01-02       A       1       1
2   2020-01-03       A       2       0
3   2020-01-04       A       0       0
4   2020-01-05       A       1       1
5   2020-01-06       A       3       0
6   2020-01-07       A       0       0
7   2020-01-08       A       0       0
8   2020-01-09       A       0       0
9   2020-01-10       A       3       0
10  2020-01-11       A       0       0
11  2020-01-12       A       1       1
12  2020-01-13       A       0       1
13  2020-01-14       A       0       1
14  2020-01-15       A       3       0
15  2020-01-16       A       0       0
16  2020-01-17       A       1       1
17  2020-01-18       A       0       1
18  2020-01-19       A       0       1
19  2020-01-20       A       1       1
20  2020-01-01       B       0       0
21  2020-01-02       B       1       1
22  2020-01-03       B       2       0
23  2020-01-04       B       0       0
24  2020-01-05       B       1       1
25  2020-01-06       B       3       0
26  2020-01-07       B       0       0
27  2020-01-08       B       0       0
28  2020-01-09       B       0       0
29  2020-01-10       B       3       0
30  2020-01-11       B       0       0
31  2020-01-12       B       1       1
32  2020-01-13       B       0       1
33  2020-01-14       B       0       1
34  2020-01-15       B       3       0
35  2020-01-16       B       0       0
36  2020-01-17       B       1       1
37  2020-01-18       B       0       1
38  2020-01-19       B       0       1
39  2020-01-20       B       1       1

2
投票

s_gen2 块中有一些令人困惑的行。我怀疑它无法编译。例如,在下面的行中,m 在赋值之前使用。

data = df[df.Machine==m]
for m in dfListU:

无论如何,既然你的机器列表已经分组,s_gen 就可以重用,只需进行调整即可保持数据帧不变。

df = pd.DataFrame([])
df['Date'] = ['2020-01-01','2020-01-02','2020-01-03','2020-01-04','2020-01-05',
            '2020-01-06','2020-01-07','2020-01-08','2020-01-09','2020-01-10',
            '2020-01-11','2020-01-12','2020-01-13','2020-01-14','2020-01-15',
            '2020-01-16','2020-01-17','2020-01-18','2020-01-19','2020-01-20',
            '2020-01-01','2020-01-02','2020-01-03','2020-01-04','2020-01-05',
            '2020-01-06','2020-01-07','2020-01-08','2020-01-09','2020-01-10',
            '2020-01-11','2020-01-12','2020-01-13','2020-01-14','2020-01-15',
            '2020-01-16','2020-01-17','2020-01-18','2020-01-19','2020-01-20']

df['Machine'] = ['A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A','A',
'B','B','B','B','B','B','B','B','B','B','B','B','B','B','B','B','B','B','B','B',]
df['Signal'] = [0,1,2,0,1,3,0,0,0,3,0,1,0,0,3,0,1,0,0,1,0,1,2,0,1,3,0,0,0,3,0,1,0,0,3,0,1,0,0,1] 

def s_gen(Signal):
    _status = 0
    status0 = []
    for (i) in Signal:
        if _status == 0:
            if i == 1:
                _status = 1 
        elif _status == 1:
            if (i == 2 or i==3):
                _status = 0
        status0.append(_status)

    return status0

unique_machines = df['Machine'].unique()

whole_status_list = []

for m in unique_machines:
    data = df[df.Machine==m]

    whole_status_list.extend(s_gen(data["Signal"]))

df["Status"] = whole_status_list

上面的代码应该会有帮助。


2
投票

您可以简化您的

s_gen
功能,因为它只需要知道是否关闭/打开机器:

def s_gen(Signal):
    _status = 0
    Status = []
    for sig in Signal:
        _status = 1 if sig == 1 else 0 if sig in [2, 3] else _status
        Status.append(_status)
    return Status

然后您可以使用

groupby
transform
来获取每台机器的状态:

df['NewStatus'] = df.groupby('Machine')['Signal'].transform(s_gen)

对于您的样本数据,我们可以检查正确性:

(df['Status'] == df['NewStatus']).all()
# True

1
投票

您可以使用

groupby
根据 "Machine" 键将数据帧拆分为不同的组,然后使用
transform
将您的函数应用到每个组的 "Signal" 列。请注意,我已修改您的
s_gen
函数以适合变换操作。

def s_gen(signal):
    _status = 0
    status0 = []
    for (i) in signal:
        if _status == 0:
            if i == 1:
                _status = 1
        elif _status == 1:
            if i == 2 or i == 3:
                _status = 0
        status0.append(_status)

    return status0

df['Status'] = df.groupby('Machine')['Signal'].transform(s_gen)

如果您想利用矢量化来加速您的过程,您可以使用以下转换:

# divide dataframe into groups based on "Machine" key
machine_groups = df.groupby("Machine")

# find out when the signal is switched (signal != 0)
df['SignalSwitch'] = (machine_groups.Signal.diff().fillna(machine_groups.Signal.transform('first')).astype(bool) & (df['Signal'] != 0))

# initialize 'Status' column
df['Status'] = None

# fill the 'Status' column with 1 if signal is 1 at the time of 'SignalSwitch', 0 if signal is 0
df.loc[df['SignalSwitch'] & (df['Signal'] == 1), 'Status'] = 1
df.loc[df['SignalSwitch'] & (df['Signal'].isin([2, 3])), 'Status'] = 0

# forward fill the remaining timestamps with previous status, fill NA (for cases when dataframe starts with signal = 0)
df['Status'] = machine_groups.Status.ffill().fillna(0)
© www.soinside.com 2019 - 2024. All rights reserved.