数据可视化:美国财富分配的直观呈现
1. 图像文件输出
当我们对直方图满意后,往往希望输出高质量的版本。在Quil中,可通过在
setup
函数里添加
q/save
调用并传入文件名,这样Quil会同时将内容输出到文件和屏幕。生成图像的格式取决于文件名后缀,具体如下:
| 文件名后缀 | 图像格式 |
| — | — |
| .tif | TIFF文件 |
| .jpg | JPEG文件 |
| .png | PNG文件 |
| .tga | TARGA文件 |
以下是一个示例代码:
(defn draw-filled-grid [{:keys [n-bins size fill-fn]}]
(let [[width height] size
x-scale (/ width n-bins)
y-scale (/ height n-bins)
setup (fn []
(doseq [x (range n-bins)
y (range n-bins)
:let [x-pos (* x x-scale)
y-pos (- height
(* (inc y) y-scale))]]
(q/fill (fill-fn x y))
(q/rect x-pos y-pos x-scale y-scale))
(q/save "heatmap.png"))]
(q/sketch :setup setup :size size)))
我们还能输出为PDF格式,后续可视化会展示相关内容。
2. 用于沟通的可视化
作为数据科学家,工作中常需与不同人群交流。亲密的同事和经理或许能读懂并解读Incanter图表,但这些图表可能难以打动CEO。有时我们还需与公众沟通。
无论何种情况,我们应专注于制作简单而强大的可视化图表,同时不牺牲数据的完整性。缺乏统计训练并不妨碍人们理解微妙细致的观点,我们应尊重受众的智慧。数据科学家面临的挑战是找到能有效传达信息的表达方式。
3. 财富分配可视化
我们要创建的可视化图表源自在线视频《美国财富不平等》(https://www.youtube.com/watch?v=QPKKQnijnsM )中的一个图表。该视频由匿名电影制作人Politizane制作,在YouTube上获得了超过1600万的点击量。
我们使用的第一组数据集来自加州大学圣克鲁兹分校心理学和社会学研究教授G. William Domhoff的文章《财富、收入和权力》(http://www2.ucsc.edu/whorulesamerica/power/wealth.html )。文章中的一张饼图展示了2010年美国人们的金融净资产细分情况:
| 百分位 | 2010年总金融财富 |
| — | — |
| 0 - 79 | 5% |
| 80 - 89 | 11% |
| 90 - 95 | 13% |
| 96 - 99 | 30% |
| 100 | 42% |
这张饼图引人关注有几个原因。首先,超过40%的金融财富被一小部分人拥有,这一概念难以理解。其次,饼图的每一块不仅代表着财富数量的巨大差异,也代表着人口数量的巨大差异,从1%的人口到80%的人口。而且,饼图通常很难解读,这张图更是如此。
一般来说,饼图不是呈现数据的好方式,即使总量在概念上代表整体的一部分。作者兼程序员Steve Fenton在https://www.stevefenton.co.uk/2009/04/pie-charts-are-bad/ 记录了很多原因并提供了合适的替代方案。
我们可以将这些数据重新解读为柱状图,以使其更易理解。以下是创建柱状图的代码:
(defn ex-10-10 []
(let [categories ["0-79" "80-89" "90-95" "96-99" "100"]
percentage [5 11 13 30 42 ]]
(-> (c/bar-chart categories percentage
:x-label "Category"
:y-label "% Financial Wealth")
(i/view))))
这个柱状图比饼图有所改进,更易于比较不同类别的相对大小。但仍存在一个显著问题:每个类别代表的人口数量差异巨大。左边的柱子代表80%的人口,而右边的柱子仅代表1%的人口。
为了让数据更易理解,我们可以将总数分成100个相等的单位,每个单位代表人口的一个百分位。每个柱子的宽度可以根据其所代表的百分位数进行调整,同时保持其面积不变。以下是实现代码:
(def wealth-distribution
(concat (repeat 80 (/ 5 80))
(repeat 10 (/ 11 10))
(repeat 5 (/ 13 5))
(repeat 4 (/ 30 4))
(repeat 1 (/ 42 1)))))
(defn ex-10-11 []
(let [categories (range (count wealth-distribution))]
(-> (c/bar-chart categories wealth-distribution
:x-label "Percentile"
:y-label "% Financial Wealth")
(i/view))))
通过这个简单的转换,我们能更好地理解真实的财富分配情况。每个柱子现在代表相等比例的人口,柱子的面积代表该百分位所拥有的财富比例。
4. 用Quil让数据生动起来
上一节的转换得到的图表过于鲜明地显示了财富分配的极端差异,除了最大的柱子外,其他柱子很难解读。一种解决方案是使用对数刻度或双对数刻度显示数据,但假设我们的目标受众是普通公众,这种方法可能不太合适。
之前的图表问题在于最右边的柱子太大,掩盖了其他所有柱子,80%的面积仅由几个像素表示。接下来,我们将使用Quil制作一个更合理利用空间、同时保持图表完整性的可视化图表。
5. 绘制不同宽度的柱子
我们将分阶段构建可视化图表。首先定义一些常量,以便根据草图的尺寸生成绘图指令:
(def plot-x 56)
(def plot-y 60)
(def plot-width 757)
(def plot-height 400)
(def bar-width 7)
以下代码将财富分配数据绘制成一系列矩形,除了最后一个柱子:
(defn draw-bars []
(let [pc99 (vec (butlast wealth-distribution))
pc1 (last wealth-distribution)
y-max (apply max pc99)
y-scale (fn [x] (* (/ x y-max) plot-height))
offset (fn [i] (* (quot i 10) 7))]
(dotimes [i 99] ;; Draw the 99%
(let [bar-height (y-scale (nth pc99 i))]
(q/rect (+ plot-x (* i bar-width) (offset i))
(+ plot-y (- plot-height bar-height))
bar-width bar-height)))
(let [n-bars 5 ;; Draw the 1%
bar-height (y-scale (/ pc1 n-bars))]
(q/rect (+ plot-x (* 100 bar-width) (offset 100))
(+ plot-y (- plot-height bar-height))
(* bar-width n-bars) bar-height))))
已绘制的柱子代表99%的人口,最后一个柱子代表1%的人口。为了使其适合我们设计的垂直比例且不超出草图顶部,我们将该柱子相应加宽,同时保持其面积不变。因此,这个柱子比其他柱子短5倍,但宽5倍。以下是生成草图的代码:
(defn ex-10-12 []
(let [size [960 540]]
(q/sketch :size size
:setup draw-bars)))
这个示例输出的图表能更清晰地显示最大柱子之间的关系,但还不太能明显看出这是一个图表。接下来,我们将添加文本以标识图表的主题和坐标轴范围。
6. 添加标题和坐标轴标签
像Incanter这样的专业可视化工具可以自动生成图表的坐标轴,但Quil没有提供这样的帮助。不过,由于我们已知柱子的宽度,实现起来并不困难。以下是添加坐标轴标签的代码:
(defn group-offset [i]
(* (quot i 10) 7))
(defn draw-axis-labels []
(q/fill 0)
(q/text-align :left)
(q/text-size 12)
(doseq [pc (range 0 (inc 100) 10)
:let [offset (group-offset pc)
x (* pc bar-width)]]
(q/text (str pc "%") (+ plot-x x offset) label-y))
(q/text "\"The 1%\"" pc1-label-x pc1-label-y))
我们还可以编写一个函数来为文本生成凸版印刷风格的浮雕效果:
(defn emboss-text [text x y]
(q/fill 255)
(q/text text x y)
(q/fill 100)
(q/text text x (- y 2)))
(defn draw-title []
(q/text-size 35)
(q/text-leading 35)
(q/text-align :center :top)
(emboss-text "ACTUAL DISTRIBUTION\nOF WEALTH IN THE US"
title-x title-y))
以下是生成包含标题和坐标轴标签的草图的代码:
(defn ex-10-13 []
(let [size [960 540]]
(q/sketch :size size
:setup #((draw-bars)
(draw-axis-labels)
(draw-title)))))
这个图表是柱子高度和面积的混合,以及自定义文本可视化的结合,在标准的图表应用程序中很难实现。使用Quil,我们可以自由地将图形和数据混合在一起。
7. 用插图提高清晰度
目前的图表还比较简陋,我们可以添加图像来增加视觉吸引力。在示例项目的资源目录中有两个SVG图像文件,一个是人物图标,另一个是从维基百科获取的美国地图。
在Quil中使用SVG图像分两步。首先,使用
q/load-shape
将图像加载到内存,该函数接受一个参数:要加载的SVG文件的路径。然后,使用
q/shape
函数将图像绘制到屏幕上,该函数需要图像的x、y位置,还可以选择指定宽度和高度。如果使用基于像素的图像(如JPEG或PNG),则应使用相应的
q/load-image
和
q/image
函数。以下是绘制图像的代码:
(defn draw-shapes []
(let [usa (q/load-shape "resources/us-mainland.svg")
person (q/load-shape "resources/person.svg")
colors [(q/color 243 195 73)
(q/color 231 119 46)
(q/color 77 180 180)
(q/color 231 74 69)
(q/color 61 76 83)]]
(.disableStyle usa)
(.disableStyle person)
(q/stroke 0 50)
(q/fill 200)
(q/shape usa 0 0)
(dotimes [n 99]
(let [quintile (quot n 20)
x (-> (* n bar-width)
(+ plot-x)
(+ (group-offset n)))]
(q/fill (nth colors quintile))
(q/shape person x icons-y icon-width icon-height)))
(q/shape person
(+ plot-x (* 100 bar-width) (group-offset 100))
icons-y icon-width icon-height)))
在这段代码中,我们对
usa
和
person
形状调用了
.disableStyle
,因为SVG文件可能包含嵌入式样式,如填充颜色、描边颜色或边框宽度信息,这些会影响Quil绘制形状的方式。我们希望完全控制表示方式,所以选择禁用所有样式。同时,我们只加载一次
person
形状,并使用
dotimes
多次绘制它,根据用户所在的五分位数设置颜色。以下是生成包含图像的草图的代码:
(defn ex-10-14 []
(let [size [960 540]]
(q/sketch :size size
:setup #((draw-shapes)
(draw-bars)
(draw-axis-labels)
(draw-title)))))
现在的图表看起来更像样了,人物图标有助于传达每个柱子代表人口的一个百分位的概念。但柱子还不够吸引人,由于每个柱子代表每个人的财富,我们可以将每个柱子表示为一堆钞票。以下是绘制钞票的代码:
(defn banknotes [x y width height]
(q/no-stroke)
(q/fill 80 127 64)
(doseq [y (range (* 3 (quot y 3)) (+ y height) 3)
x (range x (+ x width) 7)]
(q/rect x y 6 2)))
代码中需要将起始y位置调整为3的偶数倍,以确保所有钞票在偶数倍后与x轴相遇,这是从顶部到底部绘制柱子的副作用。以下是将绘制钞票函数添加到草图的代码:
(defn ex-10-15 []
(let [size [960 540]]
(q/sketch :size size
:setup #((draw-shapes)
(draw-banknotes)
(draw-axis-labels)
(draw-title)))))
这个图表现在比较完整地展示了美国的实际财富分配情况。原YouTube视频的一个优势是将实际分配与人们期望的分配和他们理想的分配进行了对比。
8. 纳入额外数据
哈佛商学院教授Michael Norton和行为经济学家Dan Ariely对5000多名美国人进行了一项研究,以评估他们对财富分配的看法。当向他们展示各种财富分配示例并要求他们识别哪个来自美国时,大多数人选择了比实际情况更平衡的分配。当被要求选择他们理想的财富分配时,92%的人选择了更公平的分配。
研究结果如下表所示:
| 五分位数 | 理想百分比 | 期望百分比 | 实际百分比 |
| — | — | — | — |
| 100th | 32.0% | 58.5% | 84.5% |
| 80th | 22.0% | 20.0% | 11.5% |
| 60th | 21.5% | 12.0% | 3.7% |
| 40th | 14.0% | 6.5% | 0.2% |
| 20th | 10.5% | 3.0% | 0.1% |
我们可以将理想和期望的分配数据绘制到现有的财富分配图表上。为了使两个数据集具有可比性,我们可以利用这个机会学习如何在Quil中绘制复杂形状。要绘制相关形状,我们需要计算高度x、y和z。假设A、B、C区域的宽度为w,则:
- (x = \frac{A}{w})
- (y = x + \frac{B - xw}{2w})
- (z = y + \frac{C - yw}{2w})
通过这些计算,我们可以得到在图表上绘制的坐标。这样,我们就能更全面地展示不同分配情况之间的差异。
数据可视化:美国财富分配的直观呈现
9. 计算复杂形状高度以绘制额外数据
为了在现有的财富分配图表上绘制理想和期望的财富分配数据,我们需要计算相关形状的高度。根据前面提到的公式,我们可以将其转化为代码来实现这些计算。以下是具体的计算过程和代码示例:
假设我们已经有了A、B、C区域的面积值,以及宽度w,我们可以通过以下代码来计算高度x、y和z:
(defn calculate-heights [A B C w]
(let [x (/ A w)
y (+ x (/ (- B (* x w)) (* 2 w)))
z (+ y (/ (- C (* y w)) (* 2 w)))]
[x y z]))
;; 示例数据
(def A 100)
(def B 200)
(def C 300)
(def w 10)
(def [x y z] (calculate-heights A B C w))
(println "高度x:" x)
(println "高度y:" y)
(println "高度z:" z)
在上述代码中,
calculate-heights
函数接受A、B、C区域的面积值和宽度w作为参数,通过公式计算出高度x、y和z,并将其作为一个向量返回。我们可以根据实际的A、B、C和w值调用这个函数,得到相应的高度值,然后使用这些高度值在图表上绘制形状。
10. 绘制理想和期望财富分配形状
在得到高度x、y和z之后,我们就可以使用Quil来绘制代表理想和期望财富分配的形状。以下是一个简单的示例代码,展示了如何使用这些高度值在图表上绘制形状:
(defn draw-ideal-expected-shapes [x y z w plot-x plot-y]
(q/stroke 0)
(q/fill (q/color 0 255 0)) ; 理想分配颜色,绿色
(q/rect plot-x plot-y w x)
(q/fill (q/color 0 0 255)) ; 期望分配颜色,蓝色
(let [y-pos (+ plot-y x)]
(q/rect plot-x y-pos w (- y x)))
(let [z-pos (+ plot-y y)]
(q/rect plot-x z-pos w (- z y))))
(defn ex-10-16 []
(let [size [960 540]
A 100
B 200
C 300
w 10
[x y z] (calculate-heights A B C w)
plot-x 56
plot-y 60]
(q/sketch :size size
:setup #((draw-bars)
(draw-axis-labels)
(draw-title)
(draw-ideal-expected-shapes x y z w plot-x plot-y)))))
在上述代码中,
draw-ideal-expected-shapes
函数接受高度x、y和z,宽度w,以及绘图的起始x和y坐标作为参数。它首先绘制代表理想财富分配的矩形,然后绘制代表期望财富分配的矩形。
ex-10-16
函数则是一个完整的Quil草图示例,它调用了之前定义的绘制柱子、坐标轴标签、标题的函数,以及新的绘制理想和期望财富分配形状的函数。
11. 总结与可视化效果
通过以上步骤,我们完成了一个较为完整的美国财富分配可视化图表。这个图表不仅展示了实际的财富分配情况,还将人们理想和期望的财富分配情况也纳入其中,通过对比可以更直观地看出不同分配之间的差异。
整个可视化过程的流程图如下:
graph LR
A[获取财富分配数据] --> B[数据预处理]
B --> C[绘制基本柱状图]
C --> D[调整柱子宽度和高度]
D --> E[添加标题和坐标轴标签]
E --> F[添加插图]
F --> G[绘制钞票表示柱子]
G --> H[纳入理想和期望分配数据]
H --> I[计算形状高度]
I --> J[绘制理想和期望分配形状]
J --> K[完成可视化图表]
在这个过程中,我们使用了Quil这个工具,它提供了很大的灵活性,让我们能够自由地将图形和数据混合在一起,实现一些在标准图表应用程序中难以实现的效果。同时,我们也遵循了可视化的原则,制作出简单而强大的图表,以有效地向不同的受众传达信息。
以下是整个过程中涉及的主要代码和功能总结:
| 功能 | 代码示例 |
| — | — |
| 输出图像文件 |
(q/save "heatmap.png")
|
| 创建基本柱状图 |
(c/bar-chart categories percentage :x-label "Category" :y-label "% Financial Wealth")
|
| 调整财富分配数据表示 |
(def wealth-distribution (concat (repeat 80 (/ 5 80)) ...))
|
| 绘制不同宽度的柱子 |
(defn draw-bars [] ...)
|
| 添加标题和坐标轴标签 |
(defn draw-axis-labels [] ...)
|
| 绘制插图 |
(defn draw-shapes [] ...)
|
| 绘制钞票表示柱子 |
(defn banknotes [x y width height] ...)
|
| 计算理想和期望分配形状高度 |
(defn calculate-heights [A B C w] ...)
|
| 绘制理想和期望分配形状 |
(defn draw-ideal-expected-shapes [x y z w plot-x plot-y] ...)
|
通过这些代码和步骤,我们可以清晰地看到美国财富分配的实际情况、人们的期望和理想状态,为进一步的分析和讨论提供了直观的依据。
超级会员免费看
23

被折叠的 条评论
为什么被折叠?



