python:对非零填充的对象使用 strptime()
python: use strptime() for object that is not zero padding
我有一个 pandas 数据框,其中有两列都是对象格式。它们包含年份(4 表示 2004)和月份。我想减去它们。
start end
4-oct 12-nov
dec-3 11-oct
jan-5 16-dec
12-oct 17-apr
我试过了:
data['end'].apply(lambda x: datetime.strptime(repr(x), "'%y-%b'"))
data['end'].apply(lambda x: datetime.strptime(repr(x), "b'%y-%b'"))
但是他们没有用。
- 如何处理第一列(“%y-%b”和“%b-%y”)中的不同格式和非零填充
- 如何将 strptime() 应用于对象格式? (repr() 可以将它们转换为字符串)吗?
您必须直接使用 %y-%b
而不是在 repr:
In [11]: df['end'].apply(lambda x: datetime.strptime(x, "%y-%b"))
Out[11]:
0 2012-11-01
1 2011-10-01
2 2016-12-01
3 2017-04-01
Name: end, dtype: datetime64[ns]
In [12]: pd.to_datetime(df["end"], format="%y-%b") # alternatively/more efficient
Out[12]:
0 2012-11-01
1 2011-10-01
2 2016-12-01
3 2017-04-01
Name: end, dtype: datetime64[ns]
一旦它们都在 pandas datetime64 系列中,您可以用 -
减去它们。
为了修复个位数年份(在开始列中),我将使用正则表达式对其进行标准化:
In [21]: df["start"].replace({"^(\d-.*)$": "0\g<1>", "^(.*)-(\d)$": "0\g<2>-\g<1>"}, regex=True)
Out[21]:
0 04-oct
1 03-dec
2 05-jan
3 12-oct
Name: start, dtype: object
然后就可以套用上面的格式了
您的代码存在多个问题。
- 您正在使用
%y
,这需要两位数的年份,但有些年份是一位数。幸运的是,那些只出现在 start
中,而你只是问如何解析 end
。但是,如果您还想解析 start
——或者如果您的真实数据只有个位数年份,则需要解决此问题。
- 您在字符串上调用
repr
,然后尝试解析字符串 repr,而不是仅仅解析字符串。 (您的字符串已经是字符串。object
是 Python 中每种类型的基础 class,包括 str
。这就是 Pandas 用于系列的内容没有它知道如何处理的好类型,比如 int64 或 datetime64——它只是存储原生 Python 对象及其拥有的任何原生 Python 类型,比如 str
。)
- 你的一些字符串是月-年格式而不是年-月,所以同样格式的字符串显然不会解析它们。您需要使用某种启发式解析器(可能来自
dateutil
),或者将它们全部预处理成相同的格式,或者编写一个尝试两种格式的函数。
- 您的其中一个字符串甚至没有有效的月份。您不能将
des-3
解析为月份和年份,因为 des
不是月份。我不确定你想做什么。也许使用非日期值?
- Apply 不会就地改变
DataFrame
,它只是 returns 您必须存储在某个地方的新 Series
。
综合起来:
def parsedate(s):
try:
return datetime.strptime(s, '%y-%b')
except ValueError:
pass
try:
return datetime.strptime(s, '%b-%y')
except ValueError:
pass
return datetime.now() # <whatever you actually want to do for des-3 here>
df.end = df.end.apply(parsedate)
这会起作用,并为您提供 Timestamp
个值,您可以将这些值相互减去以获得 Timedelta
个值。
当然它会将 des-3
变成 now()
,这可能不是您想要的;你必须决定你真正想要什么。
既然你的格式这么乱,与其试图把它塞进接近标准格式的东西然后处理一堆错误处理,不如用为你的特殊设计的代码手动解析它可能更好格式。像这样:
MONTHS = {
'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6,
'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12, 'des': 12 }
def parsedate(s):
part1, _, part2 = s.partition('-')
if part2.isdigit():
part1, part2 = part2, part1
return datetime(year=2000+int(part1), month=MONTHS[part2], day=1)
我有一个 pandas 数据框,其中有两列都是对象格式。它们包含年份(4 表示 2004)和月份。我想减去它们。
start end
4-oct 12-nov
dec-3 11-oct
jan-5 16-dec
12-oct 17-apr
我试过了:
data['end'].apply(lambda x: datetime.strptime(repr(x), "'%y-%b'"))
data['end'].apply(lambda x: datetime.strptime(repr(x), "b'%y-%b'"))
但是他们没有用。
- 如何处理第一列(“%y-%b”和“%b-%y”)中的不同格式和非零填充
- 如何将 strptime() 应用于对象格式? (repr() 可以将它们转换为字符串)吗?
您必须直接使用 %y-%b
而不是在 repr:
In [11]: df['end'].apply(lambda x: datetime.strptime(x, "%y-%b"))
Out[11]:
0 2012-11-01
1 2011-10-01
2 2016-12-01
3 2017-04-01
Name: end, dtype: datetime64[ns]
In [12]: pd.to_datetime(df["end"], format="%y-%b") # alternatively/more efficient
Out[12]:
0 2012-11-01
1 2011-10-01
2 2016-12-01
3 2017-04-01
Name: end, dtype: datetime64[ns]
一旦它们都在 pandas datetime64 系列中,您可以用 -
减去它们。
为了修复个位数年份(在开始列中),我将使用正则表达式对其进行标准化:
In [21]: df["start"].replace({"^(\d-.*)$": "0\g<1>", "^(.*)-(\d)$": "0\g<2>-\g<1>"}, regex=True)
Out[21]:
0 04-oct
1 03-dec
2 05-jan
3 12-oct
Name: start, dtype: object
然后就可以套用上面的格式了
您的代码存在多个问题。
- 您正在使用
%y
,这需要两位数的年份,但有些年份是一位数。幸运的是,那些只出现在start
中,而你只是问如何解析end
。但是,如果您还想解析start
——或者如果您的真实数据只有个位数年份,则需要解决此问题。 - 您在字符串上调用
repr
,然后尝试解析字符串 repr,而不是仅仅解析字符串。 (您的字符串已经是字符串。object
是 Python 中每种类型的基础 class,包括str
。这就是 Pandas 用于系列的内容没有它知道如何处理的好类型,比如 int64 或 datetime64——它只是存储原生 Python 对象及其拥有的任何原生 Python 类型,比如str
。) - 你的一些字符串是月-年格式而不是年-月,所以同样格式的字符串显然不会解析它们。您需要使用某种启发式解析器(可能来自
dateutil
),或者将它们全部预处理成相同的格式,或者编写一个尝试两种格式的函数。 - 您的其中一个字符串甚至没有有效的月份。您不能将
des-3
解析为月份和年份,因为des
不是月份。我不确定你想做什么。也许使用非日期值? - Apply 不会就地改变
DataFrame
,它只是 returns 您必须存储在某个地方的新Series
。
综合起来:
def parsedate(s):
try:
return datetime.strptime(s, '%y-%b')
except ValueError:
pass
try:
return datetime.strptime(s, '%b-%y')
except ValueError:
pass
return datetime.now() # <whatever you actually want to do for des-3 here>
df.end = df.end.apply(parsedate)
这会起作用,并为您提供 Timestamp
个值,您可以将这些值相互减去以获得 Timedelta
个值。
当然它会将 des-3
变成 now()
,这可能不是您想要的;你必须决定你真正想要什么。
既然你的格式这么乱,与其试图把它塞进接近标准格式的东西然后处理一堆错误处理,不如用为你的特殊设计的代码手动解析它可能更好格式。像这样:
MONTHS = {
'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6,
'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12, 'des': 12 }
def parsedate(s):
part1, _, part2 = s.partition('-')
if part2.isdigit():
part1, part2 = part2, part1
return datetime(year=2000+int(part1), month=MONTHS[part2], day=1)