【问题标题】:Turn pandas dataframe into a file-like object in memory?将 pandas 数据框转换为内存中的类似文件的对象?
【发布时间】:2016-11-07 07:52:14
【问题描述】:

我每天将大约 2 到 250 万条记录加载到 Postgres 数据库中。

然后我使用 pd.read_sql 读取这些数据以将其转换为数据框,然后我进行一些列操作和一些小的合并。我将这些修改后的数据保存为单独的表格供其他人使用。

当我执行 pd.to_sql 时,它需要很长时间。如果我保存一个 csv 文件并在 Postgres 中使用 COPY FROM,整个过程只需要几分钟,但服务器在单独的机器上,在那里传输文件很痛苦。

使用 psycopg2,看起来我可以使用 copy_expert 从批量复制中受益,但仍然使用 python。如果可能的话,我想避免编写实际的 csv 文件。我可以使用 pandas 数据框在内存中执行此操作吗?

这是我的熊猫代码示例。如果可能的话,我想添加 copy_expert 或其他东西以更快地保存这些数据。

    for date in required_date_range:
        df = pd.read_sql(sql=query, con=pg_engine, params={'x' : date})
        ...
        do stuff to the columns
        ...
        df.to_sql('table_name', pg_engine, index=False, if_exists='append',  dtype=final_table_dtypes)

有人可以帮我提供示例代码吗?我还是更喜欢使用 pandas,在内存中这样做会很好。如果没有,我会写一个 csv 临时文件并这样做。

编辑-这是我的最终代码。每个日期(数百万行)只需要几百秒,而不是几个小时。

to_sql = """使用 CSV 标头从标准输入复制 %s"""

def process_file(conn, table_name, file_object):
    fake_conn = cms_dtypes.pg_engine.raw_connection()
    fake_cur = fake_conn.cursor()
    fake_cur.copy_expert(sql=to_sql % table_name, file=file_object)
    fake_conn.commit()
    fake_cur.close()


#after doing stuff to the dataframe
    s_buf = io.StringIO()
    df.to_csv(s_buf) 
    process_file(cms_dtypes.pg_engine, 'fact_cms_employee', s_buf)

【问题讨论】:

  • 我不知道 psycopg2 但您可以尝试类似:s_buf = io.StringIO(), df.to_csv(s_buf),它将您的 df 存储在类似文件的缓冲区中。然后可能是cur.copy_from(s_buf,...) 而不是copy_expert
  • 强者成功了!不过,我仍然保持着复制专家。当我只使用普通的 pandas.to_sql 时,它只需要 100 秒,而 10000 秒。做出真实的回答,以便我接受
  • 很高兴我能帮上忙。

标签: pandas psycopg2


【解决方案1】:

Python 模块 io(docs) 具有用于类文件对象的必要工具。

import io

# text buffer
s_buf = io.StringIO()

# saving a data frame to a buffer (same as with a regular file):
df.to_csv(s_buf)

编辑。 (我忘了)为了以后从缓冲区中读取,它的位置应该设置在开头:

s_buf.seek(0)

我不熟悉psycopg2,但根据docscopy_expertcopy_from都可以使用,例如:

cur.copy_from(s_buf, table)

(对于 Python 2,请参阅 StringIO。)

【讨论】:

  • 谢谢。此外,我正在遍历每个日期以查询该日期。每次我再次连接到数据库时。有没有更好的方法不必连接/重新连接每个循环?就像我会连接一次,然后我会在循环期间更改查询?
  • 我不确定我是否理解,但您不能只查询整个内容(范围内的所有日期)一次。如果这太大,那么可能会分块查询。我想它会在数据框中添加另一列带有日期的列。然后,如果您不需要此列,则可以删除此列,或者选择并使用子帧,或者按日期groupby 并迭代组。或者,如果您想完全避免pd.read_sql,可以将带有copy_expert/copy_to 的数据复制到字符串缓冲区,然后将其加载到带有pd.read_csv 的数据帧中。它只是从我的头顶。
  • AttributeError: '_io.StringIO' object has no attribute 'write_cells'
【解决方案2】:

我在实施 ptrj 的解决方案时遇到了问题。

我认为问题源于 pandas 将缓冲区的 pos 设置到末尾。

如下:

from StringIO import StringIO
df = pd.DataFrame({"name":['foo','bar'],"id":[1,2]})
s_buf = StringIO()
df.to_csv(s_buf)
s_buf.__dict__

# Output
# {'softspace': 0, 'buflist': ['foo,1\n', 'bar,2\n'], 'pos': 12, 'len': 12, 'closed': False, 'buf': ''}

请注意 pos 为 12。我必须将 pos 设置为 0 才能使后续的 copy_from 命令起作用

s_buf.pos = 0
cur = conn.cursor()
cur.copy_from(s_buf, tablename, sep=',')
conn.commit()

【讨论】:

  • 我在周末运行了该代码,没有出现任何错误。当我星期一到达办公室时,我的桌子是空的,这很糟糕。我最终编写了完全工作的临时 csv 文件。所以如果我先做 to_csv(s_buf) 然后 s_buf.pos=0 那么它会在不写 csv 的情况下工作吗?
  • @a_bigbadwolf @trench 好点!这是文件/流缓冲区的标准行为(即不是熊猫的错)。我只是忘了包括它,我的道歉。将位置设置为开头的惯用方法是运行 s_buf.seek(0)
猜你喜欢
  • 2019-02-04
  • 2020-03-27
  • 2018-11-08
  • 2018-05-17
  • 2018-12-19
  • 2022-01-06
  • 2021-12-21
  • 1970-01-01
  • 2019-09-25
相关资源
最近更新 更多