【问题标题】:python: use strptime() for object that is not zero paddingpython:对非零填充的对象使用 strptime()
【发布时间】:2018-11-03 03:01:21
【问题描述】:

我有一个 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() 将它们转换为字符串吗)?

【问题讨论】:

  • des-3 是错字还是语言问题?
  • 首先,你为什么使用%y-%b%y 表示年份,而不是日期。其次,你为什么要解析字符串repr(在第一个例子中)和字节repr(在第二个例子中)而不是仅仅解析字符串本身, strptime(x, fmt)
  • 它们是年份,例如 3 表示 2003 年。

标签: python timestamp time-series data-mining data-cleaning


【解决方案1】:

你必须直接使用%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

那么你就可以应用上面的格式了。

【讨论】:

  • %M 表示分钟。为什么要使用它?
  • 它们都是年份,例如,4-oct 表示 2004 年 10 月。 jan-5 表示 2005 年 1 月。
  • @abarnert 啊,原来是复制粘贴
  • @AndyHayden 好的,那你为什么仍然在你的答案中有%M
  • @abarnert 对不起,我本来打算回到这个。添加了基于正则表达式的解决方案。虽然我想我还是不明白 OP 的问题 2。
【解决方案2】:

您的代码存在多个问题。

  • 您正在使用%y,这需要 2 位数的年份,但您的某些年份是 1 位数。幸运的是,那些只出现在start 中,而您只是在问如何解析end。但是,如果您还想解析 start,或者如果您的真实数据具有个位数年份,则需要解决此问题。
  • 您在字符串上调用repr,然后尝试解析字符串repr,而不是仅仅解析字符串。 (你的字符串已经是字符串了。object 是 Python 中每种类型的基类,包括 str。这就是 Pandas 用于一个系列的,它没有一个它知道如何处理的好类型,比如 int64或 datetime64——它只存储原生 Python 对象以及它们拥有的任何原生 Python 类型,例如 str。)
  • 您的某些字符串是月-年格式而不是年-月格式,因此相同格式的字符串显然不会解析它们。您需要使用某种启发式解析器(可能来自 dateutil),或者将它们全部预处理为相同的格式,或者编写一个尝试两种格式的函数。
  • 您的某个字符串甚至没有有效月份。您不能将des-3 解析为月份和年份,因为des 不是月份。我不确定你想怎么做。也许使用非日期值?
  • Apply 不会就地改变 DataFrame,它只会返回一个新的 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)

【讨论】:

  • @Juang 我的回答显示了如何将它们解析为年份,那么这条评论的意义何在?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-04-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-09
  • 2016-01-12
相关资源
最近更新 更多