在Ubuntu上进行的一项拥有1000万个数据点散点图基准测试的开源交互式绘图软件调查
受到以下用例的启发:https://stats.stackexchange.com/questions/376361/how-to-find-the-sample-points-that-have-statistically-meaningful-large-outlier-r,我对几个绘图程序进行了基准测试,使用完全相同的输入文件。
基本上,我想要:
- 绘制多维数据的XY散点图,希望以Z作为点的颜色
- 通过鼠标交互地选择一些看起来有趣的点
- 查看所选点的所有维度(至少包括X、Y和Z),以尝试理解它们为什么是XY散点图中的异常值
这个问题可以用以下简化的测试数据表示:
python -c 'for i in range(10000000): print(f"{i},{i*2},{i*4}")' > 10m1.csv
echo 5000000,20000000,-1 >> 10m1.csv
10m1.csv
(约239 MB)的前几行如下所示:
10m1.csv
0,0,0
1,2,4
2,4,8
3,6,12
4,8,16
而最后一个,第一千万个,是个例外,看起来像这样:
5000000,20000000,-1
所以我们基本上有:
- 一条斜率为2且有1000万个点的直线
- 加上一个单独的离群点,在绘图的顶部中心之外
类似于:
Y
^
|
|
| + +
|
| +
|
| +
|
| +
|
| +
|
| +
|
| +
|
| +
|
+-------------------> X
这个基准的目标是在图表中找到点(5000000,20000000),然后确定第三列的值,我们的测试中为-1
。
当我最初写下这个答案时,我使用了生成的10.csv。
python -c 'for i in range(10000000): print(f"{i},{i*2},{i*4}")' > 10m.csv
没有异常值。虽然这测试了性能,但它并未测试选择能力,因此目标是在我有动力时将每个测试迁移到10m1.csv。
我还制作了一个包含10个数据点和异常值的示例,以便评估某些无法处理1000万数据点计数的工具的可用性:
i=0;
while [ "$i" -lt 10 ]; do
echo "$i,$((2 * i)),$((4 * i))"; i=$((i + 1));
done > 11.csv
echo 5,20,-1 >> 11.csv
为了增加趣味性,我还准备了一个更大的10亿点数据集,以防任何一个程序能够处理1000万个点!CSV文件有点混乱,所以我转向了HDF5格式。
import h5py
import numpy
size = 1000000000
with h5py.File('1b.hdf5', 'w') as f:
x = numpy.arange(size + 1)
x[size] = size / 2
f.create_dataset('x', data=x, dtype='int64')
y = numpy.arange(size + 1) * 2
y[size] = 3 * size / 2
f.create_dataset('y', data=y, dtype='int64')
z = numpy.arange(size + 1) * 4
z[size] = -1
f.create_dataset('z', data=z, dtype='int64')
这将生成一个类似于10m1.csv
的约23 GiB文件,其中包含:
- 类似于
10m.csv
的一条直线上的10亿个点
- 图形中心顶部的一个异常点
我还要创建一个10m1.csv的SQLite版本,因为从实际应用角度来看,这可能是最合理的格式之一,它将允许进行良好理解的SQL查询、明确的索引控制和二进制数值数据。
f=10m.sqlite
rm -f "$f"
n=10000000
time sqlite3 "$f" 'create table t(x integer, y integer, z integer)'
time sqlite3 "$f" "insert into t select value as id, value as x, value * 2 as y, value * 2 as z from generate_series(0, $((n - 1)))"
time sqlite3 "$f" "INSERT INTO t VALUES (?, ?, ?)', ($((n/2)), $((3*n/2)), -1))"
time sqlite3 "$f" 'create index txy on t(x, y)'
我还用n = 10亿运行了那段代码,生成了一个1b.sqlite文件。到目前为止,
generate_series
是我找到的最快的插入方法:
使用Python将大量数据批量插入SQLite。
我按照(x, y)进行索引,因为这可能会加快查找工具在给定x-y矩形中获取所有点的查询速度。生成的10m1.sqlite文件约为367 MB,比CSV文件大,这是由于索引造成的。
除非在某个子部分中另有说明,测试是在Ubuntu 18.10上进行的,使用ThinkPad P51笔记本电脑,配备Intel Core i7-7820HQ CPU(4核/8线程),2x Samsung M471A2K43BB1-CRC RAM(2x 16GiB),NVIDIA Quadro M1200 4GB GDDR5 GPU。
结果摘要
根据我的非常特定的测试用例和我作为许多被评估软件的首次用户的经验,这是我观察到的情况:
它能处理1000万个点吗:
工具 |
处理10百万点吗? |
功能丰富吗? |
用户界面好用吗? |
Vaex |
是的,甚至可以处理10亿! |
是的。 |
是的,Jupyter小部件 |
VisIt |
是的,但不支持1亿 |
是的,2D和3D,专注于交互。 |
否 |
Paraview |
否 |
与上述相同,可能稍微少一些2D功能。 |
非常好 |
Mayavi |
是的 |
仅支持3D,良好的交互和脚本支持,但功能更有限。 |
可以 |
gnuplot |
勉强支持非交互模式。 |
功能丰富,但在交互模式下受限。 |
可以 |
matplotlib |
否 |
与上述相同。 |
可以 |
Bokeh |
否,最多支持100万 |
是的,易于脚本编写。 |
非常好,Jupyter小部件 |
PyViz |
? |
? |
? |
seaborn |
? |
? |
? |
sqlitebrowser |
否 |
可以可视化SQL查询结果 |
一般 |
Vaex 2.0.2
https://github.com/vaexio/vaex
按照以下链接安装并使hello world正常工作:如何在Vaex中进行交互式2D散点图缩放/点选择?
我测试了vaex,它可以处理高达10亿个数据点,真是太棒了!
它首先是“Python脚本为主”,这对于可重复性非常好,并且可以轻松地与其他Python工具进行交互。
Jupyter设置有一些复杂,但一旦我使用virtualenv运行起来,就感觉很棒。
要在Jupyter中加载我们的CSV文件,请运行以下命令:
import vaex
df = vaex.from_csv('10m.csv', names=['x', 'y', 'z'],)
df.plot_widget(df.x, df.y, backend='bqplot')
我们可以立即看到:
现在,我们可以用鼠标进行缩放、平移和选择点,更新速度非常快,全部完成不到10秒。这里我已经放大了一些个别的点,并且选中了其中几个(图像上有一个淡色矩形框)。
在使用鼠标进行选择后,这与使用
df.select()
方法具有完全相同的效果。因此,我们可以通过在Jupyter中运行以下代码来提取所选点:
df.to_pandas_df(selection=True)
输出数据格式为:
x y z index
0 4525460 9050920 18101840 4525460
1 4525461 9050922 18101844 4525461
2 4525462 9050924 18101848 4525462
3 4525463 9050926 18101852 4525463
4 4525464 9050928 18101856 4525464
5 4525465 9050930 18101860 4525465
6 4525466 9050932 18101864 4525466
自从10M点正常工作后,我决定尝试1B点...结果也很顺利!
import vaex
df = vaex.open('1b.hdf5')
df.plot_widget(df.x, df.y, backend='bqplot')
为了观察在原始图中看不见的异常值,我们可以参考
如何在vaex交互式Jupyter bqplot plot_widget中更改点的样式以使单个点更大和可见?并使用以下方法:
df.plot_widget(df.x, df.y, f='log', shape=128, backend='bqplot')
生成的结果是:
选择了这个点之后:
我们获取了异常值的完整数据。
x y z
0 500000000 1500000000 -1
这是创作者们使用更有趣的数据集和更多功能的演示:
https://www.youtube.com/watch?v=2Tt0i823-ec&t=770
不幸的是,它没有内置的sqlite支持:
https://github.com/vaexio/vaex/issues/864
在Ubuntu 19.04中进行了测试。
VisIt 2.13.3
网站:
https://wci.llnl.gov/simulation/computer-codes/visit
许可证:BSD
由
劳伦斯利弗莫尔国家实验室开发,该实验室是一个
国家核安全局的实验室,所以你可以想象如果我能让它工作,1000万个点对它来说不算什么。(书籍
《超人:西摩·克雷的故事》(查尔斯·J·默里著,1997年)很好地展示了这些计算能力饥渴的实验室在建造第一颗氢弹时的情况,因为你不能随意进行核试验,即使你这样做了,你也无法真正测量你想要的结果,因为它爆炸得太快、太热:计算机模型是必需的。他们决定,一群物理学家的妻子带着计算器是不够的,就像早期洛斯阿拉莫斯裂变弹那样。当以色列购买了他们的一台计算机时,每个人都立刻认为这是
为了制造核武器。)
安装:没有Debian软件包,只需从网站下载Linux二进制文件即可运行,无需安装。另请参阅:https://askubuntu.com/questions/966901/installing-visit
基于VTK,这是许多高性能图形软件使用的后端库。用C语言编写。
在与用户界面玩了3个小时后,我终于搞定了,并且它确实解决了我在此处详细描述的用例:https://stats.stackexchange.com/questions/376361/how-to-find-the-sample-points-that-have-statistically-meaningful-large-outlier-r
以下是它在本帖的测试数据上的外观:
一些选择和缩放功能。
这是选取窗口的位置。
就性能而言,VisIt表现得非常出色:每个图形操作要么只需要很短的时间,要么立即完成。当我需要等待时,它会显示一个“处理中”的消息,并显示剩余工作的百分比,GUI不会冻结。
由于10m个数据点运行得很好,我也尝试了1亿个数据点(一个2.7G的CSV文件),但不幸的是它崩溃/进入了奇怪的状态,我在htop
中看到4个VisIt线程占用了我全部的16GiB RAM并且可能因为内存分配失败而死机。
初始入门过程有点痛苦:
- 如果你不是核弹工程师,许多默认设置都感觉糟透了。例如:
- 默认点大小为1像素(会与我的显示器上的灰尘混淆)
- 坐标轴从0.0到1.0: 如何在Visit绘图程序上显示实际的坐标轴数值而不是0.0到1.0的分数?
- 多窗口设置时,在选取数据点时会出现讨厌的多个弹出窗口
- 显示您的用户名和绘图日期(通过"控制"->"注释"->"用户信息"中移除)
- 自动定位的默认设置很糟糕:图例与坐标轴冲突,找不到标题自动化设置,所以我不得不手动添加一个标签并重新调整所有内容的位置
- 这个软件拥有许多功能,所以很难找到你想要的功能
- 这份手册非常有帮助,
但它是一份386页的PDF巨兽,令人不安地标注着"2005年10月版本1.5"。我想知道他们是否用这个来开发Trinity! 而实际上这是一份漂亮的Sphinx HTML手册,是我最初回答这个问题后创建的
- 没有Ubuntu软件包,但预编译二进制文件可以直接使用。
我将这些问题归因于:
- 它存在了很长时间,使用了一些过时的图形用户界面(GUI)思想
- 你不能只是点击绘图元素来更改它们(例如坐标轴、标题等),而且功能很多,所以有点难找到你要找的那个
我也喜欢一些 LLNL 基础设施渗透到那个代码库中。例如,看看 docs/OfficeHours.txt 和该目录中的其他文件!对于 Brad 来说,他是“周一早上的人”真是遗憾!哦,还有答录机的密码是“Kill Ed”,别忘了。
Paraview 5.9.0
网站:https://www.paraview.org/
许可证:BSD
在 Ubuntu 20.10 上测试通过。
安装:
sudo apt install paraview
或者从网站上下载预编译版本获取最新版本。这就是我为了这篇评论所做的,因为apt只有5.7.0版本。我下载了
ParaView-5.9.0-MPI-Linux-Python3.8-64bit.tar.gz
。
由
Kitware和
洛斯阿拉莫斯国家实验室开发,后来又加入了
桑迪亚国家实验室(还有其他两个NNSA实验室),所以我们再次期望它能轻松处理数据。它也是基于VTK并用C++编写的,这更加令人期待。
然而,我感到失望:由于某种原因,1000万个点使得GUI非常缓慢和无响应,导致无法使用。每当我点击某些东西,比如隐藏线条,都要花费几十秒的时间。我认为在某个时候它出现了故障,并完全停止响应。
我对一个受控的、广告宣传充分的“我正在工作,请稍等片刻”的时刻感到满意,但是GUI在此期间冻结?这是不可接受的。
htop显示Paraview使用了8个线程和3GB的内存,所以CPU和内存都没有达到最大限制。
就GUI而言,Paraview非常漂亮和现代化,比VisIt好得多,只要不出现卡顿问题。
由于10m1.csv
导致程序崩溃,我测试了11.csv
来看看除了性能之外是否能解决我的问题,答案是肯定的:
paraview 11.csv
- 从弹出窗口中选择CSV读取器
- 在左侧应用属性
- 在Pipeline Browser上右键单击CSV文件
- 添加过滤器 > 按字母顺序 > 绘制数据。为什么绘图是一个过滤器?对于初次使用者来说不太直观,相关链接:paraview: 从CSV文件绘制数据我确定一旦你理解了过滤器的更多普遍化能做什么的概念,这些事情就会变得有意义,但还是有点费解。
- 属性 > 应用
- 取消选择"使用索引作为x轴"
- X数组名称:Field 0
- 系列参数移除Field 0和Field 2
- 选择Field 1并且:
- 线条样式:无
- 标记样式:十字
- 标记大小:根据需要增加或减小
- 在图表上方点击"矩形选择(s)"图标
- 选择异常值(点被突出显示)
- 在图表过滤器中添加另一个过滤器:"提取选择"
- 应用
最后终于!!!我得到了一个只包含选定异常值的表格,并显示"Field 2"的值为-1。
嗯,确实不是一帆风顺的过程,但我最终成功了。
另一个缺点是与VisIt相比,Paraview在功能上显得有些不足,例如:
Mayavi 4.6.2
网站:https://github.com/enthought/mayavi
开发者:Enthought
安装:
sudo apt-get install libvtk6-dev
python3 -m pip install -u mayavi PyQt5
Mayavi似乎非常专注于3D,我找不到如何在其中进行2D绘图,所以对于我的用例来说不太适合。
为了检查性能,我根据
https://docs.enthought.com/mayavi/mayavi/auto/example_scatter_plot.html中的示例进行了调整,使用了1000万个点,运行得很好,没有出现延迟。
import numpy as np
from tvtk.api import tvtk
from mayavi.scripts import mayavi2
n = 10000000
pd = tvtk.PolyData()
pd.points = np.linspace((1,1,1),(n,n,n),n)
pd.verts = np.arange(n).reshape((-1, 1))
pd.point_data.scalars = np.arange(n)
@mayavi2.standalone
def main():
from mayavi.sources.vtk_data_source import VTKDataSource
from mayavi.modules.outline import Outline
from mayavi.modules.surface import Surface
mayavi.new_scene()
d = VTKDataSource()
d.data = pd
mayavi.add_source(d)
mayavi.add_module(Outline())
s = Surface()
mayavi.add_module(s)
s.actor.property.trait_set(representation='p', point_size=1)
main()
输出:
我无法放大到足够的程度来看到个别点,近似3D平面太远了。也许有办法吗?
关于Mayavi的一个很酷的事情是开发人员在允许你从Python脚本中方便地启动和设置GUI方面投入了很多努力,就像Matplotlib和gnuplot一样。似乎在Paraview中也可能实现这一点,但至少文档不如其他两者好。
总体而言,Mayavi似乎没有VisIt/Paraview那样丰富的功能。例如,我无法直接从GUI加载CSV:如何从Mayavi GUI加载CSV文件?
Gnuplot 5.2.2
网站:http://www.gnuplot.info/
当我需要快速简单操作时,gnuplot确实很方便,而且我总是尝试它。
安装:
sudo apt-get install gnuplot
对于非交互式使用,它可以相当好地处理1000万个点:
#!/usr/bin/env gnuplot
set terminal png size 1024,1024
set output "gnuplot.png"
set key off
set datafile separator ","
plot "10m1.csv" using 1:2:3:3 with labels point
完成于7秒钟:
但是如果我试着与之互动
#!/usr/bin/env gnuplot
set terminal wxt size 1024,1024
set key off
set datafile separator ","
plot "10m.csv" using 1:2:3 palette
并且:
gnuplot -persist main.gnuplot
然后初始渲染和缩放感觉太慢了。我甚至看不到矩形选择线!
另外请注意,对于我的使用情况,我需要使用超文本标签,如:
plot "10m.csv" using 1:2:3 with labels hypertext
但是标签功能存在性能问题,包括非交互式渲染。不过我已经报告了这个问题,Ethan在一天内解决了它:
https://groups.google.com/forum/#!topic/comp.graphics.apps.gnuplot/qpL8aJIi9ZE
然而,我必须说有一个合理的解决方法可以选择异常值:只需为所有点添加带有行ID的标签!如果附近有很多点,你可能无法阅读标签。但对于你关心的异常值,你可能会成功!例如,如果我在原始数据中添加一个异常值:
cp 10m.csv 10m1.csv
printf '2500000,10000000,40000000\n' >> 10m1.csv
修改绘图命令为:
#!/usr/bin/env gnuplot
set terminal png size 1024,1024
set output "gnuplot.png"
set key off
set datafile separator ","
plot "10.csv" using 1:2:3:3 palette with labels
这个修复显著减慢了绘图速度(在上述修复之后40分钟!!!),但产生了一个合理的输出:
所以通过一些数据过滤,我们最终会达到目标。
Matplotlib 1.5.1,numpy 1.11.1,Python 3.6.7
网站:
https://matplotlib.org/
当我的gnuplot脚本变得太疯狂时,我通常尝试使用Matplotlib。
仅仅
numpy.loadtxt
就花费了大约10秒钟,所以我知道这不会顺利进行。
import numpy
import matplotlib.pyplot as plt
x, y, z = numpy.loadtxt('10m.csv', delimiter=',', unpack=True)
plt.figure(figsize=(8, 8), dpi=128)
plt.scatter(x, y, c=z)
plt.show()
首先,非交互式尝试产生了良好的输出,但耗时3分钟55秒...
然后,交互式尝试在初始渲染和缩放上花费了很长时间。无法使用:
请注意这个截图上的问题,应该立即缩放并消失的缩放选择却在屏幕上停留了很长时间,等待缩放计算完成!
为了让交互版本正常工作,我不得不注释掉
plt.figure(figsize=(8, 8), dpi=128)
,否则会出现以下错误:
RuntimeError: In set_size: Could not set the fontsize
Bokeh 1.3.1
https://github.com/bokeh/bokeh
Ubuntu 19.04 安装:
python3 -m pip install bokeh
然后启动Jupyter:
jupyter notebook
现在,如果我绘制1百万个点,一切都运行得非常完美,界面非常棒且快速,包括缩放和悬停信息。
from bokeh.io import output_notebook, show
from bokeh.models import HoverTool
from bokeh.transform import linear_cmap
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
import numpy as np
output_notebook()
N = 1000000
source = ColumnDataSource(data=dict(
x=np.random.random(size=N) * N,
y=np.random.random(size=N) * N,
z=np.random.random(size=N)
))
hover = HoverTool(tooltips=[("z", "@z")])
p = figure()
p.add_tools(hover)
p.circle(
'x',
'y',
source=source,
color=linear_cmap('z', 'Viridis256', 0, 1.0),
size=5
)
show(p)
初始视图:
在一个缩放后:
如果我提升到10米,它就会出问题,htop
显示Chromium有8个线程占用了我所有的内存,并处于不可中断的IO状态。
这是关于引用以下要点的问题:如何引用选择的Bokeh数据点
PyViz
https://pyviz.org/
待办事项:评估。
集成Bokeh + datashader + 其他工具。
视频演示10亿数据点:https://www.youtube.com/watch?v=k27MJJLJNT4 "PyViz: 使用30行Python代码可视化10亿数据点的仪表盘" 由 "Anaconda, Inc." 发布于2018-04-17。
seaborn
https://seaborn.pydata.org/
待办事项:评估。
已经有一个关于如何使用seaborn可视化至少5000万行的问答(QA)了。
sqlitebrowser 3.12.2
https://github.com/sqlitebrowser/sqlitebrowser
我试了一下,看看它能否处理10m1.sqlite,但不幸的是它不能。真可惜!
不过,它可以直接绘制查询结果,这还是挺酷的。
以下是它的外观:
在这张图片中,我将10m1.sqlite加载到工具中,然后开始浏览数据。
但它只绘制了用于浏览的数据。
您可以点击绘图下方右侧的按钮“加载所有数据并重新绘制图表”,但这会打开一个进度条,每3秒增加1%,所以前景不太乐观,我放弃了。
在Ubuntu 23.04上进行了测试。
SQL直方图查询
我想知道为什么我很难找到一个使用此作为后端的交互式UI工具。在索引数据库上使用SQL直方图感觉是最合理的方法。例如,使用10个步骤并忽略空箱:
div=10
x=0
y=0
x2=10000000
y2=20000000
dx=$(((x2 - x) / div))
dy=$(((y2 - y) / div))
time sqlite3 10m1.sqlite --cmd '.mode csv' <<EOF
select
floor(x/$dx)*$dx as x,
floor(y/$dy)*$dy as y,
count(*) as cnt
from t
where
x >= $x and x < $x2 and
y >= $y and y < $y2
group by 1, 2
order by 1, 2
EOF
我们到达:
0,0,1000000
1000000,2000000,1000000
2000000,4000000,1000000
3000000,6000000,1000000
4000000,8000000,1000000
5000000,10000000,1000000
5000000,20000000,1
6000000,12000000,1000000
7000000,14000000,1000000
8000000,16000000,1000000
9000000,18000000,1000000
查询需要6秒钟,所以它可以处理大约10百万个点,还算可以,但无法扩展到10亿个点。
既然我们只有一个点,那么我们可以在该范围内进行完整的列表。
x=5000000
y=20000000
x2=6000000
y2=40000000
time sqlite3 10m1.sqlite --cmd '.mode csv' <<EOF
select *
from t
where
x >= $x and x < $x2 and
y >= $y and y < $y2
order by x, y
EOF
立即得出最终所需的:
5000000,20000000,-1
所以,这个GUI可以有一个点的最大限制,其中:
- 如果超过了限制,使用热力图
- 否则,在该区间内查询完整的单个点,并在图中绘制单个点
如何将SQL扩展到10亿行:R-tree索引
为了扩展到10亿行,我们需要r-tree/空间索引,它们允许我们高效地对多列进行不等式操作。SQLite拥有这些功能,但使用起来有点麻烦:
尽管存在这些限制,我还是进行了一个亿级点测试,使用了重复的x/y列和以下创建时间:30分钟,文件大小:5.9 GB
然后进行了一个有上限的计数扫描:
max=100
div=10
x=0
y=0
x2=100000000
y2=200000000
dx=$(((x2 - x) / div))
dy=$(((y2 - y) / div))
cx=0
while [ $cx -lt $x2 ]; do
cy=0
while [ $cy -lt $y2 ]; do
printf "$cx,$cy,"
sqlite3 100mr.sqlite --cmd '.mode csv' <<EOF
select count(x) from (
select x from t
where
x >= $cx and x < $((cx + dx)) and
y >= $cy and y < $((cy + dy))
limit $max
)
EOF
cy=$((cy+dy))
done
cx=$((cx+dx))
done
仅用0.2秒完成,令人惊叹。如果不考虑极长的生成时间,它可能会扩展到10亿。
不幸的是,PostgreSQL索引创建速度也没有更快:如何将使用SQLite R树的简单空间索引移植到Postgres? 不过至少它支持点而不仅限于矩形。
在Ubuntu 23.04上进行了测试。