【问题标题】:Workflow for adding new columns from Pandas to SQLite tables将新列从 Pandas 添加到 SQLite 表的工作流程
【发布时间】:2017-03-26 15:28:23
【问题描述】:

设置

两个表:schoolsstudents。 SQLite 中的索引(或键)对于students 表将是idtime,对于schools 表将是schooltime。我的数据集是关于不同的东西,但我认为学生的例子更容易理解。

import pandas as pd
import numpy as np
import sqlite3

df_students = pd.DataFrame(
{'id': list(range(0,4)) + list(range(0,4)),
'time': [0]*4 + [1]*4, 'school': ['A']*2 + ['B']*2 + ['A']*2 + ['B']*2,
'satisfaction': np.random.rand(8)} )
df_students.set_index(['id', 'time'], inplace=True)

        satisfaction    school
id  time        
0   0   0.863023    A
1   0   0.929337    A
2   0   0.705265    B
3   0   0.160457    B
0   1   0.208302    A
1   1   0.029397    A
2   1   0.266651    B
3   1   0.646079    B

df_schools = pd.DataFrame({'school': ['A']*2 + ['B']*2, 'time': [0]*2 + [1]*2, 'mean_scores': np.random.rand(4)})
df_schools.set_index(['school', 'time'], inplace=True)
df_schools


               mean_scores
school  time    
A       0     0.358154
A       0     0.142589
B       1     0.260951
B       1     0.683727

## Send to SQLite3

conn = sqlite3.connect('schools_students.sqlite')

df_students.to_sql('students', conn)
df_schools.to_sql('schools', conn)

我需要做什么?

我有一堆函数在 pandas 数据帧上运行并创建新列,然后应将其插入到 schoolsstudents 表中(取决于我正在构建的内容)。一个典型的函数按顺序执行:

  1. 从两个 SQL 表中查询列
  2. 使用groupby、自定义函数的applyrolling_meanpandas函数(其中许多在SQL上不可用,或者难​​以编写)来构造一个新列。返回类型为pd.Seriesnp.array
  3. 将新列添加到适当的数据框(schoolsstudents

这些函数是在我有一个适合内存的小型数据库时编写的,因此它们是纯 pandas

这是一个伪代码示例:

def example_f(satisfaction, mean_scores)
    """Silly function that divides mean satisfaction per school by mean score"""
    #here goes the pandas functions I already wrote
    mean_satisfaction = mean(satisfaction) 
    return mean_satisfaction/mean_scores

satisf_div_score = example_f(satisfaction, mean_scores)
# Here push satisf_div_score to `schools` table

因为我的数据集非常大,我无法在内存中调用这些函数。想象一下,学校位于不同的地区。原来我只有一个区,所以我知道这些功能可以分别处理来自每个区的数据。

我认为可行的工作流程是:

  • 查询小区i相关数据
  • 将函数应用于i 区的数据并生成新列作为 np.array 或 pd.Series
  • 在适当的表格中插入此列(将填充该列中i 区的数据
  • i = 1 到K 的地区重复

虽然我的数据集在 SQLite 中(我希望它保持这种状态!)如果好处很大,我愿意将它迁移到其他东西。


我知道有不同的合理答案,但很高兴听到一些对你有用且简单的东西。谢谢!

【问题讨论】:

  • 我有点古玩。你的桌子有多大?通常 pandas 可以在崩溃之前处理许多(数百万)条目。您提到使用apply,这通常对性能不利,尽管有时是必要的。如果您真的内存不足,您的代码可能没有得到优化。寻找包含中间结果的变量,并通过尽快将它们分配给最终列来摆脱它们。否则,您建议的工作流程听起来很合理,如果这是您可以对其进行切片的最低级别。
  • 如果您不想更改所选工具,您可以将数据集拆分为多个部分,例如按地区和/或学校编号。因此,您将获得小块数据的所有导数值均值、平均值等,这些数据可以很好地存储在内存中,并且可以快速计算。然后在 for s_id in schools: data=get_data_for_school(s_id); calc(data); write_to_sql(s_id, data) 这样的伪循环中
  • 另外,如果您的日期真的很大,我想考虑使用其他一些数据库:例如 PostgreSQL。它适用于不适合内存的大量数据,并且有一个特殊的window functions 来执行滚动平均值、位置等的计算。也许它可以在没有熊猫的情况下解决您的所有任务。随时提问。
  • @EugeneLisitsky:如果你想扩展你的想法(尽可能详细),那么我可以给你赏金。我提出了一个示例数据,以使该过程更容易。我认为更详细的答案也会使其他人受益!
  • @AskeDoerge :如果您有兴趣,同样适用。

标签: python sql database sqlite pandas


【解决方案1】:

有多种方法,您可以选择更适合您的特定任务的方法:

  1. 将所有数据移至“更大”的数据库。就我个人而言,我更喜欢 PostgreSQL——它在大数据集上表现得非常好。 幸运的是,pandas 支持 SQLAlchemy - 跨数据库 ORM,因此您可以对不同的数据库使用相同的查询。

  2. 将数据分成块并分别计算任何块。我将使用 PostgreSQL 进行演示,但您可以使用任何数据库。

    from sqlalchemy import create_engine
    import psycopg2
    mydb = create_engine('postgresql://user@host.domain:5432/database')
    # lets select some groups of data into first dataframe, 
    # you may use school ids instead of my sections
    df=pd.read_sql_query('''SELECT sections, count(id) FROM table WHERE created_at <'2016-01-01' GROUP BY sections ORDER BY 2 DESC LIMIT 10''', con=mydb)
    print(df)  # don't worry about strange output - sections have type int[] and it's supported well!
    
       sections     count
    0  [121, 227]  104583
    1  [296, 227]   48905
    2  [121]        43599
    3  [302, 227]   29684 
    4  [298, 227]   26814
    5  [294, 227]   24071
    6  [297, 227]   23038
    7  [292, 227]   22019
    8  [282, 227]   20369
    9  [283, 227]   19908
    
    # Now we have some sections and we can select only data related to them
    for section in df['sections']:
       df2 = pd.read_sql_query('''SELECT sections, name, created_at, updated_at, status 
                                  FROM table 
                                  WHERE created_at <'2016-01-01'   
                                      AND sections=%(section)s 
                                  ORDER BY created_at''', 
                               con=mydb, params=dict(section=section))
        print(section, df2.std())
    
    [121, 227] status    0.478194
    dtype: float64
    [296, 227] status    0.544706
    dtype: float64
    [121] status    0.499901
    dtype: float64
    [302, 227] status    0.504573
    dtype: float64
    [298, 227] status    0.518472
    dtype: float64
    [294, 227] status    0.46254
    dtype: float64
    [297, 227] status    0.525619
    dtype: float64
    [292, 227] status    0.627244
    dtype: float64
    [282, 227] status    0.362891
    dtype: float64
    [283, 227] status    0.406112
    dtype: float64
    

    当然,这个例子是合成的——计算文章的平均状态是相当荒谬的 :) 但它演示了如何拆分大量数据并分段处理。

  3. 使用特定的 PostgreSQL(或 Oracle 或 MS 或任何你喜欢的)进行统计。这是Window Functions in PostgreSQL 上的优秀文档。幸运的是,您可以在 DB 中执行一些计算并将预制数据移动到 DataFrame,如上所述。

更新:如何将信息加载回数据库。

幸运的是,DataFrame 支持方法to_sql 让这个过程变得简单:

from sqlalchemy import create_engine
mydb = create_engine('postgresql://user@host.domain:5432/database')
df2.to_sql('tablename', mydb, if_exists='append', chunksize=100)

您可以指定您需要的操作:if_exists='append' 将行添加到表中,如果您有很多行,您可以将它们拆分为块,以便 db 可以插入它们。

【讨论】:

  • 尤金,感谢您的回答。您对创建新列并逐节填充的工作流程有什么建议吗?我在想query=''' insert or replace into NewTable (ID,Name,Age) values (?,?,?) ''' conn.executemany(query, df2.to_records(index=False))之类的东西
  • 我已经更新了食谱。另外请查看参数if_exists - 如果表已经存在,它会定义操作。我认为更好的方法是避免重复,然后尝试在数据库中解决它们。
  • 谢谢!如果这些记录已经存在,我有点担心if_exists='append'。这就是我尝试使用 insert or replace into 的原因
  • 还有replace选项:如果表存在,删除它,重新创建它,然后插入数据。
  • SQLAlchemy 支持 PostgreSQL 9.5+ 的“UPSERT”功能。因此,您可以尝试将数据插入 db 并自动更新现有行。但我从未测试过它。更多信息:docs.sqlalchemy.org/en/latest/dialects/…
猜你喜欢
  • 1970-01-01
  • 2019-12-31
  • 1970-01-01
  • 2014-11-12
  • 1970-01-01
  • 2018-02-22
  • 2016-03-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多