将值重新格式化为单独的列

Reformat values into separate columns

我正在尝试将值分隔到 pandas df 中的不同列中。具体来说,我在同一列中有代表标签和时间戳的字符串。我希望将它们分成单独的列。我只是不确定更有效的过程是什么。

对于下面的 df,我想将时间字符串分隔到一个单独的列中。

df = pd.DataFrame({
    'Value' : ['Foo X','10:00','10:00','10:00','10:00','Bar X','11:00','11:00','Cat X','12:00','12:00','12:00'],                 
    'Number' : [0,1,2,3,4,0,1,2,0,1,2,3],                      
    })

输出:

    Value  Number
0   Foo X       0
1   10:00       1
2   10:00       2
3   10:00       3
4   10:00       4
5   Bar X       0
6   11:00       1
7   11:00       2
8   Cat X       0
9   12:00       1
10  12:00       2
11  12:00       3

问题是每个标签的时间戳数量不同,所以我不能只拆分每第 n 行。例如

df1 = pd.DataFrame({'Value':df['Value'].iloc[:1:4].values, 'Time':df['Value'].iloc[:1:4].values})

另一种尝试可能是创建一个单独的列来传递来自 df.Value 的所有值,然后用 np.nan 替换所有时间戳并对输出进行子集化。但是我不确定这是否非常有效?

    Value  Number   Time
0   Foo X       0  Foo X
1   10:00       1  10:00
2   10:00       2  10:00
3   10:00       3  10:00
4   10:00       4  10:00
5   Bar X       0  Bar X
6   11:00       1  11:00
7   11:00       2  11:00
8   Cat X       0  Cat X
9   12:00       1  12:00
10  12:00       2  12:00
11  12:00       3  12:00

预期输出:

  Value  Number   Time
0  Foo X       1  10:00
1  Foo X       2  10:00
2  Foo X       3  10:00
3  Foo X       4  10:00
4  Bar X       1  11:00
5  Bar X       2  11:00
6  Cat X       1  12:00
7  Cat X       2  12:00
8  Cat X       3  12:00

下面的函数应该会给你想要的输出。

def process_dataframe(df):
    s = df.loc[df.Number==0]['Value']
    labels = s.to_list()
    a = s.index.to_list()
    a.append(df.index.size)
    repnum = [x2 - x1 - 1 for x1,x2 in zip(a[:-1], a[1:])]
    df2 = df.loc[df['Number']!=0].copy()
    df2['Time'] = df2['Value']
    df2['Value'] = s.repeat(repnum).to_list()
    return df2
process_dataframe(df)

Output

  Value  Number   Time
0  Foo X       1  10:00
1  Foo X       2  10:00
2  Foo X       3  10:00
3  Foo X       4  10:00
4  Bar X       1  11:00
5  Bar X       2  11:00
6  Cat X       1  12:00
7  Cat X       2  12:00
8  Cat X       3  12:00

您可以使用 groupby with pd.Series.repat 创建 Value 列 然后 select TimeNumber 通过使用 boolean indexing:

value_bool=pd.Series(['X' in key for key in df['Value']])
Value=df.loc[value_bool]['Value'] #selecting values ​​for the Value column
groups=df.groupby(value_bool.cumsum())
new_df=Value.repeat(groups.size()-1).to_frame().reset_index(drop=True) #create dataframe with new Value Column
new_df[['Number','Time']]=df.loc[~value_bool].reset_index(drop=True).reindex(columns=['Number','Value']) #creating Number and Time

输出:

   Value  Number   Time
0  Foo X       1  10:00
1  Foo X       2  10:00
2  Foo X       3  10:00
3  Foo X       4  10:00
4  Bar X       1  11:00
5  Bar X       2  11:00
6  Cat X       1  12:00
7  Cat X       2  12:00
8  Cat X       3  12:00

想法是通过 to_datetimeerrors='coerce' 来区分时间值,用于缺少不匹配的值:

mask = pd.to_datetime(df['Value'], errors='coerce').notna()

Series.str.contains 用于测试模式 2 位数字 ::

mask = df['Value'].str.contains(r'\d{2}:\d{2}')

或 id 可能测试不等于 0:

mask = df['Number'].ne(0)

然后创建新列并将 Value 替换为 NaNs by mask with Series.mask and forward filling missing values, last filter by boolean indexing:

df['Time'] = df['Value']
df['Value'] = df['Value'].mask(mask).ffill()
df = df[mask].copy()
print (df)
    Value  Number   Time
1   Foo X       1  10:00
2   Foo X       2  10:00
3   Foo X       3  10:00
4   Foo X       4  10:00
6   Bar X       1  11:00
7   Bar X       2  11:00
9   Cat X       1  12:00
10  Cat X       2  12:00
11  Cat X       3  12:00

另一种使用遮罩和填充的解决方案:

(
    df.assign(ind=df.Value.mask(df.Value.str.contains('^\d+:\d+')).ffill())
    .loc[lambda x: x.Number.ne(0)]
    .set_axis(['Time','Number', 'Value'], axis=1, inplace=False)
)