用 IPython notebook + nvd3 实现交互式调试
发布于:2014/08/31 18:22:52
IPython(http://ipython.org/) 是一款 Python 的增强IDE,而 IPython notebook 则是在网页上实现了更为强大的交互功能的 IPython
本文的内容旨在简要的介绍使用 IPython notebook 的方法,并重点介绍利用 IPython notebook + nvd3 图形库进行交互式的调试
一、准备
# 安装 IPython 及 nvd3
$ sudo pip install ipython python-nvd3
# 安装 numpy、scipy、matplotlib
$ sudo pip install numpy scipy matplotlib
# 打开 IPython
$mkdir~/notebook
$ ipython notebook ~/notebook
点击New Notebook打开一个新页面,就算准备完成了
在Untitled0那一栏可以自由设定文件名,按command + s就可以保存
二、为数值运算做准备
数值运算会需要用到 numpy、scipy、matplotlib 这些常用库,直接在 IPython notebook 里写一句%pylab inline就自动的导入了这些库
在 IPython 里,代码被分为一个一个的 cell ,按shift + ENTER可以执行当前选定的 cell, 也可以在菜单里点选 CELL -> Run ALL 从上至下运行所有的 cell,在运行大段的代码的时候,当你改变了某个上游的代码块,可能不仅仅需要重新 Run 这个或全部的 cell,还需要点击 Kernel Restart 来重启后台,来清理掉命名空间中之前留下的各种变量或导入的模块
三、简单的绘图
因为之前使用 %pylab inline 已经自动引入了 numpy 和 matplotlib 库,现在可以直接在 notebook 里绘图了
Ipython %pylab 会自动引入 numpy,相当于执行了 from numpy import *; import numpy as np ,所以你既可以直接用 numpy 里的内容,也可以按照习惯用 np.xxx 的方式来使用
# 在 IPython notebook 里绘图x = np.linspace(0,10,50)y = np.sin(x)z = np.cos(x)plot(x, y)scatter(x, z)
plot 和 scatter 都是 matplotlib 中的接口,你可以在下面的地址中获取到更多的信息
本文的目的并不是介绍 matplotlib 绘图,所以先跳过了
四、使用 IPython 的交互式功能
IPython 提供了功能强大的交互式操作 iteract 接口,使用户可以通过交互式的操作来调节参数
# 引入 interact 模块fromIPython.html.widgetsimportinteract, IntSliderWidget# 把之前的画图函数改写成依赖于参数的函数deftest_interact(min_, max_, steps_):x = np.linspace(min_, max_, steps_) y = np.sin(x) z = np.cos(x) plot(x, y) scatter(x, y)# 使用 interact 来交互式的调试参数interact(test_interact,# 为每一个参数设定一个 interact 控件min_=IntSliderWidget(min=1,# 最小值max=10,# 最大值step=1,# 每次调节的步长value=1),# 初始值max_=IntSliderWidget(min=10, max=20, step=1, value=10), steps_=IntSliderWidget(min=10, max=100, step=10, value=50))
我们现在可以用鼠标拖动来简单的调节效果了
IPython 的 widgets 还有
Widget, DOMWidget, CallbackDispatcher
CheckboxWidget, ToggleButtonWidget
ButtonWidget
ContainerWidget, PopupWidget
FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
ImageWidget
IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
TabWidget, AccordionWidget
HTMLWidget, LatexWidget, TextWidget, TextareaWidget
interact, interactive, fixed
官方文档(http://ipython.org/ipython-doc/dev/api/index.html)的资料少的可怜,我一般都是上这里http://nullege.com/codes/search/IPython.html.widgets.DropdownWidget来查看接口的用法,其实大部分的用法都比较一致,就是传递一个 min, max, step 值, dropdown 传的是 dict 或 list,每个接口都有一个 value 参数来设定初始值
其实常用的就是 IntSliderWidget、FloatSliderWidget、DropdownWidget、RadioButtonsWidget ,用法也不难,试一试就出来了,这里也就不详细介绍了
五、使用 nvd3
现在我们已经会用 matplotlib 画图,而且还会使用 active 来进行交互式的调试,其实已经能解决日常中的绝大部分需求了。不过在 IPython notebook 中使用 matplotlib 有一个美中不足的地方就是不能对图形进行交互式操作,当我们绘制一个三维图的时候,可能需要拖动才能看清楚图形的结构,而 IPython 中的 matplotlib 对此就无能为力了。不过好在 IPython 强大到可以近乎完美的支持 HTML,也就是说你可以随意的去挑选你喜欢的 JavaScript 图形库来进行绘图。这里我们选择在科学数据绘制上表现出众的 D3 (http://d3js.org/)
nvd3(http://nvd3.org/)是对 d3 的封装,而 python-nvd3 则是一个能够生成 nvd3 网页代码(html & javascript)的 python 模块,我们用 python-nvd3 来生成 HTML 代码,然后用 IPython 的 HTML 模块提供的功能来显示
OK,图形按照我们所想的画出来了~
和 matplotlib 相比,图形更加美观,而且更重要的是有了交互功能
*六、nvd3 的高级使用
python-nvd3 提供的接口实在少的可怜。比如如果我想改变 tooltip 的内容让它显示我希望的内容,或者我希望在一个数据集内也有不同的显示方式(比如不同 size 或颜色等等),这些在 nvd3 中都是可以实现的功能,但是 python-nvd3 却没有提供相应的接口
好在 python-nvd3 返回了 HTML 代码,我的解决办法是直接用正则去匹配 HTML 代码,然后插入我想要实现功能的 javascript 代码
我也在探索更好的实现办法,所以这段就不详细介绍了,如果你只是迫切的需要类似的功能,可以考虑使用我目前的解决办法
1、替换 tooltip
# 这个函数的功能是替换 python-nvd3 生成的 HTML代码# 参数 nodes 是一个形如 [{‘id’: name, ‘x’: 1, 'y': 2, 'value': 23}, {}, ...] 的数据块# 让 tooltip 去遍历 nodes,找出 x 和 y 匹配的 node, 然后显示其 id 值defreplace_tooltip(html, nodes):"""Change the D3 chart's tooltip
"""tooltip_content ="""
chart.tooltipContent(function(key, y, e, graph) {{
var nodes = {0};
var content = '';
var group = Number(key.split('_')[1]);
var x = graph.point.x;
var y = graph.point.y;
for(var i in nodes){{
var point = nodes[i];
if(point['x'] == x && point['y'] == y){{
content += '' + point['id'] + '';
break;
}}
}}
content += '
' + x.toString() + ' ' + y.toString();
return content;
}});
""".format(str(nodes)) p = re.compile(r'\bchart.tooltipContent[.\S\s]*''return tooltip_str;\s*\}\);') html = p.sub(tooltip_content, html)returnhtml
2、让同组数据有不同的显示效果
# 这个函数也是替换 python-nvd3 生成的 HTML# nodes 的结构同上# anomaly 是一个形如 [id, id] 的列表# 其实原理和上面的一样,都是根据 x 和 y 匹配,然后修改 nvd3 的数据集 DATA_TCS (TCS是我的画布名,这个的命名规则是 DATA_,你可以去看自己的 python-nvd3 生成的 HTML 代码)defmark_anomalies_axis(nodes, anomalies, html):"""Mark the anomalies points
"""anoma_ls = []foranomalyinanomalies:fornodeinnodes:ifnode['id'] == anomaly: anoma_ls.append({'x': node['x'],'y': node['y']}) _mark_anoma_js ="""
anomalies = {0};
// mark anomalies
for(var ia in anomalies) {{
anoma = anomalies[ia];
for(var ig in data_TCS) {{
var group = data_TCS[ig];
for(var ip in group['values']) {{
var p = group['values'][ip];
if(p['x']==anoma['x'] && p['y']==anoma['y']) {{
p['size'] = 0.9;
break;
}}
}}
}}
}}
nv.addGraph(function() {{
""".format(str(anoma_ls)) p = re.compile(r'\bnv\.addGraph\(function\(\) {') html = p.sub(_mark_anoma_js, html)returnhtml
效果图