csv Python -将同一天(日期)拆分为多列

20jt8wwn  于 2022-12-06  发布在  Python
关注(0)|答案(5)|浏览(149)

我正在使用Pandas,我有一列考勤时间戳数据(日期)。我想将同一天的值拆分为(InTime)和(OutTime)列。

df = df[['Date']]

Date
Thu 1/09 9:10 AM
Thu 1/09 6:10 PM
Fri 2/09 9:04 AM
Fri 2/09 6:02 PM

我正在努力实现以下结果。

In Time            OutTime
Thu 1/09 9:10 AM   Thu 1/09 6:10 PM 
Fri 2/09 9:04 AM   Fri 2/09 6:02 PM
  • 谢谢-谢谢

编辑

谢谢你的帮助。问题是数据并不像它应该的那样整洁。缺少上班时间/下班时间或接近重复的记录。+我是一个基本的python用户,我甚至不能理解代码来修改它以满足我的要求。
我请求看看完整的场景和我迄今为止为达到预期结果所做的尝试。
这是一个考勤机数据,其中用户要么忘记了标记考勤或机器创建了一个重复的条目较长的拇指印象。因此,提供的代码混乱了上午和下午列,无论它发现任何重复或丢失的记录。
示例数据如下所示

User    Date
11  Thu 1/09 9:10 AM
3   Thu 1/09 9:10 AM
4   Thu 1/09 9:10 AM
2   Thu 1/09 9:23 AM
5   Thu 1/09 9:39 AM    
... ...   ...

12  Fri 30/09 5:55 PM
5   Fri 30/09 6:01 PM
6   Fri 30/09 6:04 PM
11  Fri 30/09 6:09 PM

我正在尝试为每个用户创建多个.csv文件,其中In和Out时间戳位于单独的列中包括非连续天数的空记录,因此可以将其粘贴到已创建的Excel模板中。

df = pd.read_csv('input.csv', encoding="utf-8", sep=',')
df = df[["User", "Date"]]
dataframe = pd.DataFrame(df,
                         columns=['User', 'Date'])
users = {
    'falcon': 2,
    'charlie': 3,
}

for username, ID in users.items():
    df = dataframe.loc[dataframe['User'] == ID]
    df = df[['Date']]
    df.to_csv(username + ".csv", encoding="utf-8", sep=',', header=False, index=False)

测试结果

Thu 1/09 9:10 AM
Thu 1/09 6:11 PM
Fri 2/09 9:18 AM    //Missing PM
Sat 3/09 10:44 AM
Sat 3/09 6:00 PM
Mon 5/09 9:22 AM    //Missing PM
Tue 6/09 9:09 AM
Tue 6/09 6:25 PM
Wed 7/09 9:18 AM
Wed 7/09 6:33 PM

我尝试将这些日期分开,以便将带有AM / PM的条目在其相应的列中分开(包括缺失日期的空记录)。

gg0vcinb

gg0vcinb1#

以下是pandas.DataFrame.joinpandas.DataFrame.shift的解决方案:

new_df = (
            df.add_suffix('_In_time')
              .join(df.shift(-1).add_suffix('_Out_time'))
              .iloc[::2]
         )
#输出:
print(new_df)

       Date_In_time     Date_Out_time
0  Thu 1/09 9:10 AM  Thu 1/09 6:10 PM
1  Fri 2/09 9:04 AM  Fri 2/09 6:02 AM
luaexgnf

luaexgnf2#

代码

  • 注意:这段代码是根据原始帖子编写的(请参阅下面的更新部分)。*
import pandas as pd
from io import StringIO
from dateutil.parser import parse

data = '''User,Date
1,Thu 1/09 9:10 AM
1,Thu 1/09 6:11 PM
1,Fri 2/09 9:18 AM
1,Sat 3/09 10:44 AM
1,Sat 3/09 6:00 PM
1,Mon 5/09 9:22 AM
1,Tue 6/09 9:09 AM
1,Tue 6/09 6:25 PM
1,Wed 7/09 9:18 AM
1,Wed 7/09 6:33 PM
2,Thu 1/09 9:13 AM
2,Thu 1/09 6:10 PM
2,Fri 2/09 9:10 AM
2,Fri 2/09 6:10 PM
2,Sat 3/09 10:40 AM
2,Sat 3/09 5:55 PM
2,Tue 6/09 6:21 PM
2,Wed 7/09 9:10 AM
2,Wed 7/09 6:30 PM
'''

df = pd.read_csv(
    StringIO(data), 
    parse_dates=[1], 
    date_parser=lambda x: parse(x, dayfirst=True)
)

df['Day'] = df['Date'].dt.date
df['InOut'] = df['Date'].dt.strftime('%p').map({'AM':'In time', 'PM':'Out time'})
output = df.pivot(index=['User','Day'], columns='InOut',values='Date')

users = df['User'].unique()
days = pd.date_range(start=df['Day'].min(), end=df['Day'].max(), freq='D')
index = pd.MultiIndex.from_product([users, days])
output = output.reindex(index, fill_value=pd.NA)

for user_id, group in output.groupby(level=0):
    group.to_csv(
        f'user_{user_id}.csv', 
        index=False, 
        date_format='%a %d/%m %I:%M %p'
    )

备注

假设我们有一些csv格式的数据:
| 用户名|日期|
| - -|- -|
| 四十二|9月5日星期一上午10:50|
在这里,用户包含一个用户id,日期的组成部分是Weekday、Day/Month、Hour:Minute、AM/PM(默认年份为2022,即当前年份)。
我们可以用pandas.read_csv读取这些数据,用dateutil.parser.parse解析日期值,参数dayfirst=True默认为False(dateutil作为Pandas的依赖项安装):

df = pd.read_csv(
    StringIO(data), 
    parse_dates=[1], 
    date_parser=lambda x: parse(x, dayfirst=True)
)

现在,我们有了正确的df['Date']类型,并可以对其应用Series.dt函数的子集:

df['Day'] = df['Date'].dt.date
df['InOut'] = df['Date'].dt.strftime('%p').map({'AM':'In time', 'PM':'Out time'})

这里,df['Date'].dt.strftime('%p')返回AM或PM时间值,用于将时间Map为In或Out值,有关详细信息,请参阅格式代码。
通过这些附加列,我们可以获得接近所需的输出by pivoting

output = df.pivot(index=['User','Day'], columns='InOut',values='Date')

下一步是插入缺失的日期。为此,我们可以应用reindex,用更宽的索引替换原始索引。对于后者,我们根据唯一用户和从第一个可能的日期到最后一个可能的日期的日期间隔的乘积创建一个多索引:

users = df['User'].unique()
days = pd.date_range(
    start=df['Day'].min(),   # from the first day
    end=df['Day'].max(),     # to the last possible day
    freq='D'                 # with frequency 1 day
)   
index = pd.MultiIndex.from_product([users, days])
output = output.reindex(index, fill_value=pd.NA)

现在我们有了这个输出数据:

要将它们保存为每个用户所需的csv文件格式,让我们按照output的多索引的第一级对output进行分组,并将每个组保存在一个for循环中:

for user_id, group in output.groupby(level=0):
    group.to_csv(
        f'user_{user_id}.csv', 
        index=False, 
        date_format='%a %d/%m %I:%M %p'
    )

这里,我们使用index=False来删除索引,使用'%a %d/%m %I:%M %p'将日期保存为Weekday Day/Month Hour:Minute (AM/PM),有关详细信息,请参阅前面提到的格式代码。

更新

在阅读了你的真实的数据之后,我得出了一个结论,你的实际问题是数据统一和清理。

关于统一

在原帖中,日期表示为Thu 1/09 9:10 AM,其中 daymonth 之前,但真实的数据中的日期总是表示为 monthday 之前,例如Sun 9/01 9:10 AM表示9月1日,9/13/22 9:05 AM表示9月13日。或者2022-09-02 09:18:42,即9月2日。这意味着您在这里得到的答案可能不适用于您的 * 真实的 * 数据。
此外,您还在一个文件中以两种不同的格式表示了日期:Sun 9/01 9:10 AM vs. 9/13/22 9:05 AM,这肯定会导致严重的问题与任何回答您的问题。
所以在我看来,你首先需要做的是统一你文件中的日期格式。

关于清洁

我认为这不是一个如何删除重复的问题。至于 * 接近重复的记录 *,这是由你来决定如何定义什么是 * 接近 *,以及如何将这些接近的记录重新编辑为一个,无论是 * 最小 最大 平均 * 等。我敢肯定,清理数据不是一个问题,为StackOverflow;这看起来更像是一个自由职业者的任务。

关于按AM/PM确定到达/离开时间

这种方法也会导致错误。你有一些到达时间在12:00:00之后的记录,或者离开时间在12:00:00之前的记录。所以这里的任何答案都不会覆盖这些记录。也许更好的方法是确定哪个差异是有意义的,并在此基础上提取到达和离开的时间。你还必须决定如何解释中午前后的时间,以防它是某个用户在某些天的单个时间记录。

fkaflof6

fkaflof63#

查看您共享的csv,很明显,日期格式存在问题(不一致,但目前还不是问题),并且存在重复条目。
请注意,有些行缺少AM时间,有些行缺少PM时间。假设某个用户在上午上班,下午下班,我做了一个简单的循环,检查每个用户的上班和下班时间,如果缺少条目,则添加“missing”。为每个用户生成一个 Dataframe ,然后将所有 Dataframe 连接起来。如果需要,由您对最终的 Dataframe 进行排序。但首先要规范数据格式,我没有做这一步,因为这不是问题的一部分,没有它也能解决问题。

import pandas as pd

input_csv = '/Users/alec/Downloads/python-split-dates-data/sandbox.csv'
df = pd.read_csv(input_csv)
print(df)

     User              Date
0      11  Sun 9/01 9:10 AM
1       3  Sun 9/01 9:10 AM
2       4  Sun 9/01 9:10 AM
3       2  Sun 9/01 9:23 AM
4       5  Sun 9/01 9:39 AM
..    ...               ...
339    12   9/30/22 5:55 PM
340     5   9/30/22 6:01 PM
341     6   9/30/22 6:04 PM
342    11   9/30/22 6:09 PM
343     4   9/30/22 6:19 PM
[344 rows x 2 columns]


cleaned_df = []
for user, group in df.drop_duplicates().groupby('User'):
    am = [] #store AM entries
    pm = [] #store PM entries
    for i, row in group.iterrows():
        if row.Date.endswith('AM'):
            am.append(row.Date)
        elif row.Date.endswith('PM'):
            while len(am) <= len(pm):
                am.append('missing IN') #add missing
            while len(pm) < len(am)-1:
                pm.append('missing OUT') #add missing
            pm.append(row.Date)
    assert len(am) == len(pm) #double check data is consistent
    _df = pd.DataFrame({'User':[user]*len(am), 'IN':am, 'OUT':pm})
    cleaned_df.append(_df)
    
new_df = pd.concat(cleaned_df)
print(new_df)

    User                IN               OUT
0      2  Sun 9/01 9:23 AM  Sun 9/01 5:55 PM
1      2  Wed 9/02 9:20 AM  Wed 9/02 5:46 PM
2      2  Wed 9/03 9:24 AM  Wed 9/03 5:46 PM
3      2  Mon 9/05 9:23 AM  Mon 9/05 5:40 PM
4      2  Thu 9/06 9:26 AM  Thu 9/06 5:49 PM
..   ...               ...               ...
0     12        missing IN   9/23/22 5:54 PM
1     12  9/27/22 10:24 AM   9/27/22 1:34 PM
2     12   9/28/22 9:01 AM   9/28/22 5:51 PM
3     12   9/29/22 9:08 AM   9/29/22 5:52 PM
4     12   9/30/22 9:16 AM   9/30/22 5:55 PM

[182 rows x 3 columns]
ar7v8xwq

ar7v8xwq4#

另一种方法,将列转换为日期时间并使用它。请尝试:

df
    Date
0   Thu 1/09 9:10 AM
1   Thu 1/09 6:10 PM
2   Fri 2/09 9:04 AM
3   Fri 2/09 6:02 PM

df['Date'] = pd.to_datetime(df['Date'] + ' 2022', format='%a %d/%m %H:%M %p %Y')
df['day'] = df['Date'].dt.date
df['time'] = df['Date'].dt.time

df.groupby('day').agg(InTime=('time', 'first'), OutTime=('time', 'last')).reset_index()

    day         InTime      OutTime
0   2022-09-01  09:10:00    06:10:00
1   2022-09-02  09:04:00    06:02:00
qlckcl4x

qlckcl4x5#

基于pandas.DataFrame.pivot的可能解决方案:

# separate day from time
df[['Date1', 'Date2']] = df['Date'].str.split('(?<=\d)\s(?=\d)', expand=True)

# create column with colnames for the new columns to be created by pivot
df['names'] = ['inTime', 'OutTime'] * (len(df)//2)
(df.pivot(index='Date1', columns='names', values='Date')
 .reset_index(drop=True).iloc[:,::-1])

输出量:

names            inTime           OutTime
0      Fri 2/09 9:04 AM  Fri 2/09 6:02 PM
1      Thu 1/09 9:10 AM  Thu 1/09 6:10 PM

如果数据集没有从早到晚的时间顺序,则解决方案可能如下:

# separate day from time
df[['Date1', 'Date2', 'Date3']] = df['Date'].str.split(
    '(?<=\d)\s(?=\d)|\s(?=.M$)', expand=True)

# this is needed if the times are no sorted in the initial dataset
df = df.sort_values(['Date1', 'Date3', 'Date2'])

# create column with colnames for the new columns to be created by pivot
df['names'] = ['inTime', 'OutTime'] * (len(df)//2)
(df.pivot(index='Date1', columns='names', values='Date')
 .reset_index(drop=True).iloc[:,::-1])

完整代码:

import pandas as pd
import numpy as np
from io import StringIO

text = """
Date
Thu 1/09 9:10 AM
Thu 1/09 6:10 PM
Fri 2/09 9:04 AM
Fri 2/09 6:02 PM
"""

df = pd.read_csv(StringIO(text), sep='\s{2,}', engine='python')

# separate day from time
df[['Date1', 'Date2']] = df['Date'].str.split('(?<=\d)\s(?=\d)', expand=True)

# create column with colnames for the new columns to be created by pivot
df['names'] = ['inTime', 'OutTime'] * (len(df)//2)
df = (df.pivot(index='Date1', columns='names', values='Date')
      .reset_index(drop=True).iloc[:,::-1])
print(df)

相关问题