背景
我正在与加拿大水资源调查公司的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融化函数。
因此,我认为我目前的流程与它的目标一样好。
答案 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更快。