熊猫:日期转换缓慢



我正在阅读一个巨大的CSV,其中包含格式为 YYYYMMDD 的日期字段,我在阅读时使用以下 lambda 进行转换:

import pandas as pd
df = pd.read_csv(filen,
                 index_col=None,
                 header=None,
                 parse_dates=[0],
                 date_parser=lambda t:pd.to_datetime(str(t),
                                            format='%Y%m%d', coerce=True))

不过,此功能非常慢。

有什么改进建议吗?

注意:正如@ritchie46的答案所述,此解决方案可能是多余的,因为根据默认为True的新参数cache_dates pandas 版本 0.25

尝试使用此函数解析日期:

def lookup(date_pd_series, format=None):
    """
    This is an extremely fast approach to datetime parsing.
    For large data, the same dates are often repeated. Rather than
    re-parse these, we store all unique dates, parse them, and
    use a lookup to convert all dates.
    """
    dates = {date:pd.to_datetime(date, format=format) for date in date_pd_series.unique()}
    return date_pd_series.map(dates)

像这样使用它:

df['date-column'] = lookup(df['date-column'], format='%Y%m%d')

基准:

$ python date-parse.py
to_datetime: 5799 ms
dateutil:    5162 ms
strptime:    1651 ms
manual:       242 ms
lookup:        32 ms

来源:https://github.com/sanand0/benchmarks/tree/master/date-parse

> @EdChum很好的建议!正如@EdChum所建议的那样,使用infer_datetime_format=True可以明显更快。下面是我的例子。

我有一个来自传感器日志的温度数据文件,如下所示:

RecNum,Date,LocationID,Unused
1,11/7/2013 20:53:01,13.60,"117","1",
2,11/7/2013 21:08:01,13.60,"117","1",
3,11/7/2013 21:23:01,13.60,"117","1",
4,11/7/2013 21:38:01,13.60,"117","1",
...

我的代码读取 csv 并解析日期 ( parse_dates=['Date'] )。使用infer_datetime_format=False,需要8分8秒

Tue Jan 24 12:18:27 2017 - Loading the Temperature data file.
Tue Jan 24 12:18:27 2017 - Temperature file is 88.172 MB.
Tue Jan 24 12:18:27 2017 - Loading into memory. Please be patient.
Tue Jan 24 12:26:35 2017 - Success: loaded 2,169,903 records.

使用 infer_datetime_format=True 时,需要 13 秒

Tue Jan 24 13:19:58 2017 - Loading the Temperature data file.
Tue Jan 24 13:19:58 2017 - Temperature file is 88.172 MB.
Tue Jan 24 13:19:58 2017 - Loading into memory. Please be patient.
Tue Jan 24 13:20:11 2017 - Success: loaded 2,169,903 records.

除非你坚持使用一个非常旧的熊猫版本,0.25之前,否则这个答案不适合你。

此处描述的功能已在 0.25 版中合并到 pandas 中

通过缓存简化日期解析

读取

所有数据然后转换数据总是比在读取 CSV 时转换慢。因为如果您立即这样做,则不需要遍历所有数据两次。您也不必将其作为字符串存储在内存中。

我们可以定义自己的日期解析器,该解析器利用缓存来存储它已经看到的日期。

import pandas as pd
cache = {}
def cached_date_parser(s):
    if s in cache:
        return cache[s]
    dt = pd.to_datetime(s, format='%Y%m%d', coerce=True)
    cache[s] = dt
    return dt
    
df = pd.read_csv(filen,
                 index_col=None,
                 header=None,
                 parse_dates=[0],
                 date_parser=cached_date_parser)

与@fixxxer的答案具有相同的优势,只需解析每个字符串一次,额外的好处是不必读取所有数据然后解析它。节省您的内存和处理时间。

从 pandas 版本 0.25 开始,函数pandas.read_csv接受 cache_dates=boolean (默认为 True) 关键字参数。因此,无需像在接受的答案中那样编写自己的缓存函数。

无需指定date_parser,pandas 能够毫无困难地解析它,而且它会快得多:

In [21]:
import io
import pandas as pd
t="""date,val
20120608,12321
20130608,12321
20140308,12321"""
df = pd.read_csv(io.StringIO(t), parse_dates=[0])
df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 3 entries, 0 to 2
Data columns (total 2 columns):
date    3 non-null datetime64[ns]
val     3 non-null int64
dtypes: datetime64[ns](1), int64(1)
memory usage: 72.0 bytes
In [22]:
df
Out[22]:
        date    val
0 2012-06-08  12321
1 2013-06-08  12321
2 2014-03-08  12321

尝试标准库:

import datetime
parser = lambda t: datetime.datetime.strptime(str(t), "%Y%m%d")

但是,我真的不知道这是否比熊猫快得多。

既然你的格式这么简单,那怎么办

def parse(t):
     string_ = str(t)
     return datetime.date(int(string_[:4]), int(string[4:6]), int(string[6:]))

编辑您说您需要处理无效数据。

def parse(t):
     string_ = str(t)
     try:
         return datetime.date(int(string_[:4]), int(string[4:6]), int(string[6:]))
     except:
         return default_datetime #you should define that somewhere else

总而言之,我对你的问题的有效性有点矛盾:

  • 您需要快速,但您仍然从CSV获取数据
  • 您需要快速,但仍需要处理无效数据

这有点矛盾;我个人在这里的方法是假设你的"巨大"CSV只需要被带入一次性能更好的格式,你要么不应该关心转换过程的速度(因为它只发生一次),要么你应该带上任何产生CSV的东西给你更好的数据 - 有很多格式不依赖于字符串解析。

如果您的日期时间具有 UTC 时间戳,而您只需要其中的一部分。将其转换为字符串,对所需内容进行切片,然后应用以下内容以加快访问速度。

created_at
2018-01-31 15:15:08 UTC
2018-01-31 15:16:02 UTC
2018-01-31 15:27:10 UTC
2018-02-01 07:05:55 UTC
2018-02-01 08:50:14 UTC
df["date"]=  df["created_at"].apply(lambda x: str(x)[:10])

df["date"] = pd.to_datetime(df["date"])

我有一个 ~150k 行的 csv。在尝试了这篇文章中的几乎所有建议后,我发现以下方法提高了 25%:

  1. 使用 Python3.7 本机csv.reader逐行读取文件
  2. 使用 float() 和 转换所有 4 个数字列
  3. 使用 datetime.datetime.fromisoformat() 解析日期列

看哪:

  1. 最后将列表转换为数据帧 (!**

让我感到困惑,这怎么能比本地大熊猫更快pd.read_csv(......有人可以解释一下吗?

最新更新