SQLite查询性能,转置,融合和熊猫

时间:2018-04-19 21:30:57

标签: python pandas sqlite merge melt

背景

我正在与加拿大水资源调查公司的HyDat Database 8000多个水文站合作。我已编写代码来查询每日流量数据:

conn = create_connection('db/Hydat.sqlite3')
cur = conn.cursor()
cur.execute("SELECT * FROM DLY_FLOWS WHERE STATION_NUMBER=?", (station,))

将返回的数据放入数据框后:

rows = cur.fetchall()    
column_headers = [description[0] for description in cur.description]
df = pd.DataFrame(rows, columns=column_headers)    

数据大致采用以下格式:

STATION_NUM  YEAR  MONTH ...  FLOW1  FLAG1  FLOW2  FLAG2  ...  
02QC003      1965  02    ...  32.5   E      33.4   A      ...
02QC003      1965  03    ...  44.6   E      45.4   A      ...
02QC003      1965  04    ...  54.3   E      56.2   A      ... 
...          ...   ...   ...  ...    ...    ...    ...    ...

FLOW N 和FLAG N 列中的 N 每个从1到31对应于该月的日期(需要过滤步骤后几个月,<31天。

我尝试将查询和重塑数据的性能提高到以下每日时间序列格式:

STATION_NUM  YEAR  MONTH  DAY  FLOW  FLAG
02QC003      1965  02     1    32.5  E
02QC003      1965  02     2    33.4  A
02QC003      1965  02     3    33.7  A
...          ...   ...    ...  ...   ...

我尝试转置到行的每日值的数量高达~1000(大致相当于100年,其中一行代表一个月,每日值代表一列)。处理一些查询不是问题,但我的目标是~40M查询。目前,我使用Pandas melt函数,首先是日常流程,然后是数据标记(为简洁起见未显示):

id_var_headers = column_headers[:11]

all_val_vars = [e for e in column_headers if 'FLOW' in e]
flow_val_vars = [e for e in all_val_vars if '_' not in e]

df_flows = pd.melt(df,
                   id_vars=id_var_headers,
                   value_vars=flow_val_vars,
                   value_name='DAILY_FLOW', 
                   var_name='DAY').sort_values(by=['YEAR', 'MONTH'])

df_flows['DAY'] = df_flows['DAY'].apply(
    map_day_to_var_name)

def map_day_to_var_name(s):
    if re.search('\d', s):
        return s[re.search('\d', s).span()[0]:]

我发现整个序列中第二个最慢的操作是在10 ^ -3秒内运行,而限制步骤是melt函数,它看起来大约慢10倍。 。我希望在这一步或更好的步骤上实现10倍的提升。

我尝试以此为契机,了解有关SQLite的更多信息,并花了一些时间试图弄清楚我如何构建查询以查看&#39;转换&#39;我的ETL过程的一部分可以合并为一步,并且比Pandas表现更好。我想出的是理论上的工作(参见this SQLFiddle),但我很难在我的代码中实现它。 有关详细信息,请参阅2018-05-02更新。

This answer from user piRSquared似乎与我之后的情况非常接近,尽管我仍然坚持groupby功能步骤。按照piRSquared的回答中列出的步骤,我希望年份和月份能够扩展到取消每日价值,这让我相信我错误地应用了groupby功能。

非常感谢任何帮助,以及关于我如何提出问题的任何反馈(这是我第一次发帖提问)。

2018-04-20更新wide_to_long

虽然我需要添加几个步骤,但斯科特波士顿的建议更加整洁:

首先,我填补了本月的日子&lt; 10,否则日期将无法退回。 df.rename(columns={'FLOW1': 'FLOW01', ...}, inplace=True)

因为我需要一个DAY列,所以我也遗漏了.drop('VARIABLE', axis=1)来制作该行:

df = pd.wide_to_long(raw_df, ['FLOW', 'FLOW_SYMBOL'], idx_cols, 'DAY', sep='', suffix='.').reset_index()

在每日记录76K的记录上进行测试,melt函数得到~0.1s,wide_to_long函数得到~0.6s。

还有其他方法可以改善这一步吗?

2018-05-02更新

我回去检查了大致代表边界长度的查询的响应时间。对于~10行(短记录)的查询,以及&gt;之一1K行(大致是数据库中记录的最长时间段),每个查询的范围分别为0.04到0.1秒。这个结果告诉我,一个更好的SQL查询不会比一个简单的查询更好,后面跟着pandas融化函数。

因此,我认为我目前的流程与它的目标一样好。

2 个答案:

答案 0 :(得分:0)

我认为你需要的是pd.wide_to_long

鉴于df:

  STATION_NUM  YEAR  MONTH  FLOW1 FLAG1  FLOW2 FLAG2
0     02QC003  1965      2   32.5     E   33.4     A
1     02QC003  1965      3   44.6     E   45.4     A
2     02QC003  1965      4   54.3     E   56.2     A

使用pd.wide_to_long

pd.wide_to_long(df,['FLOW','FLAG'],['STATION_NUM','YEAR','MONTH'],'VARIABLE',sep='',suffix='.')\
  .reset_index().drop('VARIABLE', axis=1)

输出:

  STATION_NUM  YEAR  MONTH  FLOW FLAG
0     02QC003  1965      2  32.5    E
1     02QC003  1965      2  33.4    A
2     02QC003  1965      3  44.6    E
3     02QC003  1965      3  45.4    A
4     02QC003  1965      4  54.3    E
5     02QC003  1965      4  56.2    A

答案 1 :(得分:0)

您是否考虑过31 SELECT个,每天一列,然后UNION将所有日期放在一起?它肯定会有大量的重复SQL,而且我不太了解它是否会比Pandas更快。