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'"))

但是他们没有用。

  1. 如何处理第一列(“%y-%b”和“%b-%y”)中的不同格式和非零填充
  2. 如何将 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)