博客 / 工程

使用 ClickHouse 分析 Hugging Face 数据集

author avatar
Dale McDiarmid
2023年8月29日 - 38 分钟阅读

简介

在 ClickHouse,我们赞赏开源社区及其为创新所做的贡献。随着行业对机器学习以及最近对大型语言模型 (LLM) 的关注,Hugging Face 已成为推动该领域创新和协作的重要社区。Hugging Face 提供了一个平台,机器学习社区可以在模型、数据集和应用程序方面进行协作。

之前的博客文章中,我们探讨了 clickhouse-local – ClickHouse 的一个版本,它被设计和优化用于使用笔记本电脑或工作站上的本地计算资源进行数据分析。对于任何希望使用 SQL 对文件执行数据分析任务的人来说,此工具非常棒,它提供与服务器安装相同的查询功能,但以单个可执行二进制文件的形式提供。Hugging Face 提供的以及社区贡献的数据集为分析提供了有趣的机会,我们可以使用 clickhouse-local 工具执行此分析。

在本博客文章中,我们将展示仅使用 SQL 分析 Hugging Face 托管数据集是多么容易。作为其中的一部分,我们对 Spotify 曲目数据集执行一些简单的查询,然后再探索 ClickHouse 一些更有趣的统计函数。最后,我们创建一个简单的可重用 UDF,仅通过数据集名称查询任何 Hugging Face 数据集。

Hugging Face API

Hugging Face 致力于提供文档完善且一致的 API,这对于其被采用以及作为共享模型和数据集的事实标准手段的地位至关重要。Datasets Server 提供了一个 Web API,用于可视化和探索所有类型的数据集 - 计算机视觉、语音、文本和表格。

除了公开 REST API 以便与 clickhouse-local 轻松集成之外,所有数据集在上传时也会自动转换为 Parquet 格式。ClickHouse 原生支持此格式作为一等公民,并且持续努力以提高读取和写入性能。

访问 Hugging Face 上数据集页面的用户可以使用页面顶部的“自动转换为 Parquet”按钮查看可用的 Parquet 文件。

Markdown Image

此列表由上面提到的 REST API 提供支持。要获取任何数据集的 Parquet 文件列表,我们可以简单地使用以下端点。

https://datasets-server.huggingface.co/parquet?dataset=<dataset name>

在下面的示例中,我们使用 curl 列出上面显示的 blog_authorship_corpus 数据集的文件。

curl -s 'https://datasets-server.huggingface.co/parquet?dataset=blog_authorship_corpus' | jq
{
  "parquet_files": [
	{
  	"dataset": "blog_authorship_corpus",
  	"config": "blog_authorship_corpus",
  	"split": "train",
  	"url": "https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/train/0000.parquet",
  	"filename": "0000.parquet",
  	"size": 301216503
	},
	{
  	"dataset": "blog_authorship_corpus",
  	"config": "blog_authorship_corpus",
  	"split": "train",
  	"url": "https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/train/0001.parquet",
  	"filename": "0001.parquet",
  	"size": 152312736
	},
	{
  	"dataset": "blog_authorship_corpus",
  	"config": "blog_authorship_corpus",
  	"split": "validation",
  	"url": "https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/validation/0000.parquet",
  	"filename": "0000.parquet",
  	"size": 24997972
	}
  ],
  "pending": [],
  "failed": [],
  "partial": false
}

从上面的响应中,我们可以看到每个 Parquet 文件都作为 json 对象提供,下载链接通过 url 字段提供。

使用 clickhouse-local

对于我们所有的示例,我们都使用 clickhouse-local 的控制台模式。对于希望将 clickhouse-local 合并到脚本中的 Linux 管理员或用户,任何查询都可以通过 --query 参数传递,响应由 stdout 提供。

在我们查询任何 Parquet 文件之前,让我们确认我们可以使用 ClickHouse 中的上述 API,方法是使用 url 函数。为了响应的一致性,我们请求将输出呈现为 JSON。

SELECT json
FROM url('https://datasets-server.huggingface.co/parquet?dataset=blog_authorship_corpus', 'JSONAsString')
FORMAT Vertical

Row 1:
──────
json: json: {"parquet_files":[{"dataset":"blog_authorship_corpus","config":"blog_authorship_corpus","split":"train","url":"https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/train/0000.parquet","filename":"0000.parquet","size":301216503},{"dataset":"blog_authorship_corpus","config":"blog_authorship_corpus","split":"train","url":"https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/train/0001.parquet","filename":"0001.parquet","size":152312736},{"dataset":"blog_authorship_corpus","config":"blog_authorship_corpus","split":"validation","url":"https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/validation/0000.parquet","filename":"0000.parquet","size":24997972}],"pending":[],"failed":[],"partial":false}

1 row in set. Elapsed: 1.220 sec.

仔细检查 url,我们可以看到文件位于路径 https://hugging-face.cn/datasets/ 下。这似乎在数据集之间是一致的。

数据集

对于我们的示例数据集,我们使用 maharshipandya/spotify-tracks-dataset。这表示一个 Spotify 曲目数据集,其中每一行都包含给定曲目在 125 种不同流派范围内的信息。每个曲目都具有与其关联的音频特征,例如其持续时间、流派和节奏。使用上面的 url 函数和 JSON_QUERY 函数,我们可以提取可用于此数据集的 Parquet 文件的简洁列表。

SELECT JSON_QUERY(json, '$.parquet_files[*].url') AS urls
FROM url('https://datasets-server.huggingface.co/parquet?dataset=maharshipandya/spotify-tracks-dataset', 'JSONAsString')

┌─urls──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ ["https://hugging-face.cn/datasets/maharshipandya/spotify-tracks-dataset/resolve/refs%2Fconvert%2Fparquet/default/train/0000.parquet"] │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

1 row in set. Elapsed: 0.908 sec.

只有一个文件,这代表了 Hugging Face 中最容易使用的数据集之一。在这种情况下,我们可以直接查询文件 - 再次使用 url 函数。

SELECT count()
FROM url('https://hugging-face.cn/datasets/maharshipandya/spotify-tracks-dataset/resolve/refs%2Fconvert%2Fparquet/default/train/0000.parquet')
SETTINGS enable_url_encoding = 0, max_http_get_redirects = 1

┌─count()─┐
│  114000 │
└─────────┘

1 row in set. Elapsed: 1.280 sec. Processed 77.00 thousand rows, 8.81 MB (60.16 thousand rows/s., 6.88 MB/s.)

我们需要指定参数 max_http_get_redirects = 1。这确保我们遵循重定向到 Parquet 文件 url 返回的 CDN 路径。23.7 中引入的参数 enable_url_encoding = 0 确保不对 URL 应用解码 - 路径中的转义字符是故意的,应保留。对于所有后续查询,假设这些参数已在会话中设置。

除了需要为本博客文章添加参数 enable_url_encoding 之外,我们还发现使用 ClickHouse 读取 Hugging Face Parquet 文件比预期的要慢。这归因于小行组,每个行组都发出单独的 HTTP 请求。这已在问题 53069 中得到解决。

为了简化后续请求,我们可以创建一个 url 表引擎来抽象 url。这允许我们在所有后续查询中使用表 spotify 查询数据集。此表将在 clickhouse-local 会话的生命周期内存在。

SET max_http_get_redirects = 1
SET enable_url_encoding = 0

CREATE TABLE spotify
ENGINE=URL('https://hugging-face.cn/datasets/maharshipandya/spotify-tracks-dataset/resolve/refs%2Fconvert%2Fparquet/default/train/0000.parquet') 

SELECT count()
FROM spotify

┌─count()─┐
│  114000 │
└─────────┘

1 row in set. Elapsed: 0.838 sec. Processed 39.00 thousand rows, 4.51 MB (46.52 thousand rows/s., 5.37 MB/s.)

以下所有查询都使用上面创建的 spotify 表。

探索数据集

为了识别可用的列,我们可以依靠 ClickHouse 的类型推断功能并发出 DESCRIBE 查询。

DESCRIBE TABLE spotify

┌─name─────────────┬─type──────────────┬
│ Unnamed: 0   	│ Nullable(Int64)      │
│ track_id     	│ Nullable(String)     │
│ artists      	│ Nullable(String)     │
│ album_name   	│ Nullable(String)     │
│ track_name   	│ Nullable(String)     │
│ popularity   	│ Nullable(Int64)      │
│ duration_ms  	│ Nullable(Int64)      │
│ explicit     	│ Nullable(Bool)	   │
│ danceability 	│ Nullable(Float64)    │
│ energy       	│ Nullable(Float64)    │
│ key          	│ Nullable(Int64)      │
│ loudness     	│ Nullable(Float64)    │
│ mode         	│ Nullable(Int64)      │
│ speechiness  	│ Nullable(Float64)    │
│ acousticness 	│ Nullable(Float64)    │
│ instrumentalness │ Nullable(Float64) │
│ liveness     	│ Nullable(Float64)    │
│ valence      	│ Nullable(Float64)    │
│ tempo        	│ Nullable(Float64)    │
│ time_signature   │ Nullable(Int64)   │
│ track_genre  	│ Nullable(String)     │
└──────────────────┴───────────────────┴

21 rows in set. Elapsed: 0.000 sec.

感兴趣的用户可以在此处找到这些列的完整描述。当我们在下面使用列且其内容不明显时,我们将提供描述。

简单查询

在使用更复杂的统计函数详细分析数据集之前,用户通常需要执行简单的查询来了解数据。

聚合

聚合有助于了解每列中的常用值。下面,我们从数据集中识别出受欢迎的艺术家

SELECT count() AS c, artists
FROM spotify
GROUP BY artists
ORDER BY c DESC
LIMIT 20

┌───c─┬─artists─────────┐
│ 279 │ The Beatles 	│
│ 271 │ George Jones	│
│ 236 │ Stevie Wonder   │
│ 224 │ Linkin Park 	│
│ 222 │ Ella Fitzgerald │
│ 217 │ Prateek Kuhad   │
│ 202 │ Feid        	│
│ 190 │ Chuck Berry 	│
│ 183 │ Håkan Hellström │
│ 181 │ OneRepublic 	│
└─────┴─────────────────┘

20 rows in set. Elapsed: 0.828 sec. Processed 114.00 thousand rows, 13.05 MB (137.73 thousand rows/s., 15.77 MB/s.)

我们稍后的分析通常会关注音乐流派之间的差异。理想情况下,这些数据因此在流派之间均匀分布,如 0 方差所示,这应该使我们能够自信地进行一些后续的统计测量。使用 SQL 和 varPop 函数确认这一点非常简单。

SELECT count(), track_genre
FROM spotify
GROUP BY track_genre
LIMIT 10

┌─count()─┬─track_genre─┐
│	1000  │ indie   	│
│	1000  │ salsa   	│
│	1000new-age 	│
│	1000  │ swedish 	│
│	1000  │ j-dance 	│
│	1000  │ garage  	│
│	1000  │ latino  	│
│	1000  │ malay   	│
│	1000  │ rock    	│
│	1000  │ sad     	│
└─────────┴─────────────┘

10 rows in set. Elapsed: 0.848 sec. Processed 39.00 thousand rows, 4.48 MB (45.97 thousand rows/s., 5.28 MB/s.)

SELECT uniqExact(track_genre)
FROM spotify

┌─uniqExact(track_genre)─┐
│                	114  │
└────────────────────────┘


1 row in set. Elapsed: 0.822 sec. Processed 114.00 thousand rows, 13.05 MB (138.62 thousand rows/s., 15.87 MB/s.)

SELECT varPop(c)
FROM
(
	SELECT
    	count() AS c,
    	track_genre
	FROM spotify GROUP BY track_genre
)

┌─varPop(c)─┐
│     	0   │
└───────────┘


11 row in set. Elapsed: 0.881 sec. Processed 39.00 thousand rows, 4.51 MB (44.26 thousand rows/s., 5.11 MB/s.)

我们可以看到每个流派都有 1000 行。方差为 0 证明对于数据集中的所有 114 个流派都是相同的。

直方图

任何早期数据分析通常都涉及构建直方图以显示值的分布并识别可能的概率分布。例如,让我们考虑列 danceability

Danceability 考虑了曲目在多大程度上适合跳舞,这是基于音乐元素(包括节奏、节奏稳定性、节拍强度和整体规律性)的组合。值为 0.0 表示最不适合跳舞,值为 1.0 表示最适合跳舞。

可以使用 bar 函数轻松构建直方图。下面,我们按 danceability 分组(四舍五入到小数点后 1 位),并绘制计数。这为我们提供了值的分布。

SELECT
	round(danceability, 1) AS danceability,
	bar(count(), 0, max(count()) OVER ()) AS dist
FROM spotify
GROUP BY danceability
ORDER BY danceability ASC

┌─danceability─┬─dist─────────────────────────────────────────────────────────────────────────────┐
│       0      │ ▍                                                                            	  │
│      	0.1    │ ████▎                                                                        	  │
│      	0.2    │ █████████████▍                                                               	  │
│      	0.3    │ ████████████████████████                                                     	  │
│      	0.4    │ ████████████████████████████████████████████▋                                	  │
│      	0.5    │ ████████████████████████████████████████████████████████████████████▊        	  │
│      	0.6    │ ████████████████████████████████████████████████████████████████████████████████ │
│      	0.7    │ ██████████████████████████████████████████████████████████████████████       	  │
│      	0.8    │ ██████████████████████████████████████████                                   	  │
│      	0.9    │ ██████████▋                                                                  	  │
│       1      │ ▌                                                                            	  │
└──────────────┴──────────────────────────────────────────────────────────────────────────────────┘

11 rows in set. Elapsed: 0.839 sec. Processed 39.00 thousand rows, 4.48 MB (46.51 thousand rows/s., 5.34 MB/s.)

上面我们使用窗口函数 max(count()) OVER () 来确定每个组的最大计数,从而避免了我们需要将常量指定为 bar 函数的上限。

直方图的价值之一是它们能够帮助快速直观地确定值是否呈正态分布,从而为应用其他统计技术打开可能性。下面,我们使用相同的查询探索其他一些列。

Energy 是一个从 0.0 到 1.0 的度量,表示强度和活动的感知度量。通常,充满活力的曲目感觉快速、响亮和嘈杂。例如,死亡金属具有高能量,而巴赫序曲在该尺度上得分较低

┌─energy─┬─dist─────────────────────────────────────────────────────────────────────────────┐
│  	0    │ ███████▍                                                                     	│
│	0.1  │ ███████████████▎                                                             	│
│	0.2  │ ████████████████████▌                                                        	│
│	0.3  │ ███████████████████████████▉                                                 	│
│	0.4  │ █████████████████████████████████████▌                                       	│
│	0.5  │ ███████████████████████████████████████████████▌                             	│
│	0.6  │ █████████████████████████████████████████████████████████▎                   	│
│	0.7  │ ███████████████████████████████████████████████████████████████████▌         	│
│	0.8  │ ██████████████████████████████████████████████████████████████████████▏      	│
│	0.9  │ ████████████████████████████████████████████████████████████████████████████████ │
│  	1    │ ███████████████████████████████████████▊                                     	│
└────────┴──────────────────────────────────────────────────────────────────────────────────┘

Liveliness 衡量录音中观众的存在。较高的 liveness 值表示曲目是现场表演的可能性增加。高于 0.8 的值强烈表明该曲目是现场表演

这似乎不太可能呈正态分布,因为大多数音乐都是在录音棚录制的 - 导致左偏。

┌─liveness─┬─dist─────────────────────────────────────────────────────────────────────────────┐
│    	0  │ ███▍                                                                         	  │
│  	  0.1  │ ████████████████████████████████████████████████████████████████████████████████ │
│  	  0.2  │ ████████████████████████▎                                                    	  │
│  	  0.3  │ █████████████████▊                                                           	  │
│  	  0.4  │ █████████▌                                                                   	  │
│  	  0.5  │ ██▉                                                                          	  │
│  	  0.6  │ ██▌                                                                          	  │
│  	  0.7  │ ██▉                                                                          	  │
│  	  0.8  │ █▊                                                                           	  │
│  	  0.9  │ ██▏                                                                          	  │
│    	1  │ █▍                                                                           	  │
└──────────┴──────────────────────────────────────────────────────────────────────────────────┘

这并非总是构建直方图的最有效方法。这里我们确定了范围 (0-1) 并控制了间隔 (0.1),但四舍五入到小数点后一位。这在此固定尺度上效果很好,但在我们不知道范围时更具挑战性。对于数据范围不太了解或固定的列,我们可以使用 histogram 函数。在这里,我们只需指定所需的桶数,然后重用 bar 进行绘制。

例如,让我们考虑 loudness。

Loudness 是曲目的整体响度,以分贝 (dB) 为单位

WITH (
    	SELECT histogram(20)(loudness)
    	FROM spotify
	) AS hist
SELECT
	round(arrayJoin(hist).1) AS lower,
	round(arrayJoin(hist).2) AS upper,
	bar(arrayJoin(hist).3, 0, max(arrayJoin(hist).3)) AS bar
ORDER BY arrayJoin(hist).1 ASC

┌─lower─┬─upper─┬─bar──────────────────────────────────────────────────────────────────────────────┐
│   -50-48 │                                                                              	   │
│   -48-45 │                                                                              	   │
│   -45-41 │                                                                              	   │
│   -41-38 │                                                                              	   │
│   -38-35 │ ▎                                                                            	   │
│   -35-32 │ ▍                                                                            	   │
│   -32-30 │ ▊                                                                            	   │
│   -30-27 │ █                                                                            	   │
│   -27-25 │ █▋                                                                           	   │
│   -25-21 │ ███▎                                                                         	   │
│   -21-18 │ █████▋                                                                       	   │
│   -18-15 │ ██████▉                                                                      	   │
│   -15-13 │ ██████████▉                                                                  	   │
│   -13-11 │ ██████████████████████                                                       	   │
│   -11-8  │ ████████████████████████████████████████████▏                                	   │
│	-8-6  │ ████████████████████████████████████████████████████████████████▉            	   │
│	-6-3  │ ████████████████████████████████████████████████████████████████████████████████ │
│	-3-1  │ ██████████████████▏                                                          	   │
│	-12   │ █                                                                            	   │
│ 	25   │                                                                              	   │
└───────┴───────┴──────────────────────────────────────────────────────────────────────────────────┘

20 rows in set. Elapsed: 0.883 sec. Processed 114.00 thousand rows, 13.05 MB (129.03 thousand rows/s., 14.77 MB/s.)

Sparkbars

虽然直方图很棒,但假设我们想了解子集中特定列的值分布。例如,曲目时长如何随流派变化?我们可以相当乏味地为每个流派生成一个直方图并进行比较。sparkbar 函数允许我们在单个查询中更有效地执行此操作。

下面,我们可视化每个流派的曲目长度分布。这需要我们按流派和曲目长度分组(四舍五入到最接近的 10 秒)。我们使用此子查询的结果为每个流派构建一个 spark bar,其中包含 50 个桶。

SELECT
	track_genre,
	sparkbar(40)(CAST(duration_ms, 'UInt32'), c) AS distribution
FROM
(
	SELECT
    	track_genre,
    	count() AS c,
    	duration_ms
	FROM spotify
	GROUP BY
    	track_genre,
    	round(duration_ms, -4) AS duration_ms
	ORDER BY duration_ms ASC
) WHERE (duration_ms >= 60000) AND (duration_ms <= 600000)
GROUP BY track_genre
ORDER BY track_genre ASC

┌─track_genre───────┬─distribution─────────────────────────────┐
│ acoustic      	│ ▁▁▁▁▁▂▃▄▅▅▇▆█▇▆▄▃▂▁▂▁▁▁▁▁ ▁  ▁     	▁  │
│ afrobeat      	│ ▁▁ ▁▁▂▂▃▄▅▆▇▇█▆▅▃▂▃▂▂▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ │
│ alt-rock      	│ ▁  ▁▁▂▂▄▅▇█▇▇▅▄▃▂▂▂▁▁▁▁▁▁ ▁▁▁▁  ▁  ▁   ▁ │
│ alternative   	│ ▁ ▁▂▂▂▃▅▆▅▇▇█▆▄▅▄▂▂▂▁▁▁▁▁▁▁▁   ▁▁▁▁ ▁  ▁ │
│ ambient       	│ ▁▂▃▂▄▄▄▅▆▅█▅▅▄▅▄▅▄▂▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁ │
│ anime         	│ ▁▂▃▃▃▃▃▃▄▆▄▅▆▆█▆▅▄▂▂▁▂▂▁▁▁▁ ▁ ▁  ▁  ▁  ▁ │
│ black-metal   	│ ▁▁▁▁▂▂▃▂▃▄▄█▆▅▆▇▇▆▆▅▆▄▄▃▃▃▂▃▂▂▂▁▂▁▁▁▁▁▁▁ │
│ bluegrass     	│ ▁▁▁▂▃▃▅▆▇▇▇█▆▄▄▄▃▃▂▂▂▁▁▁▁▁ ▁▁▁▁▁▁▁▁▁ ▁▁▁ │
│ blues         	│ ▁▁▁▃▂▃▇█▅▅▆▄▄▄▃▃▃▂▂▁▂▁▁▁▁▁▁▁▁ ▁  ▁  ▁  ▁ │
│ brazil        	│ ▁  ▁▁▂▃▅▅▇█▆▇▆█▆▅▅▄▃▃▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁ │
│ breakbeat     	│ ▁▁ ▁▁▁▁▁▃▄▇▆▅▇▇█▆▅▅▅▃▄▂▃▃▃▂▂▂▂▁▁▂▁▁ ▁ ▁▁ │
│ british       	│ ▁▁▂▁▃▄▅▇▆▆▅█▆▆▅▃▃▃▂▁▁▁▁▂▁▁▁▁▁ ▁▁▁▁	▁▁ │
│ cantopop      	│ ▁▁▁▁▁▂▃▃▂▂▃▅▆█▆▆▄▃▂▁▁▁▁▁   	▁   	▁  │
│ chicago-house 	│ ▁ ▁ ▁▁▂▂▂▄▃▅▄▅▅▅▆▆▇▇▆▅█▆▆▆▅▄▃▃▃▃▂▃▁▂▂▁▁▁ │
│ children      	│ ▄▅▅▇▆ ▆▅▆█▆ ▅▆▅▅▄ ▄▂▃▂ ▁▁▁▁▁ ▁▁ ▁▁ ▁▁  ▁ │
│ chill         	│ ▁▁▂▃▂▅▄▇▆█▇▇▄▆▄▃▂▂▂▂▁▁▁▁▁ ▁▁▁▁▁  ▁ 	▁  │
│ classical     	│ ▇█▇▂▂▂▄▄▃▃▃▃▂▃▃▂▂▃▃▂▂▁▁▁▂▁▂▁▁▂▁▁▁▁▁▁ ▁▁▁ │
│ club          	│ ▁▁▁▂▃▃▄▄▄▆▇▇▆█▆▅▄▃▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁   ▁  ▁ │
│ comedy        	│ ▄▃▄▆▆▇█▇▇▆▇▆▅▅▅▃▆▄▃▂▃▃▁▁▂▂▂▂▁▁▁▁▁ ▁▁ ▁▁▁ │
│ country       	│ ▁   ▂▂▂▄▆▆█▇▆▆▃▂▂▂▁▁ ▁▁▁▁ ▁  ▁     	▁  │
│ dance         	│ ▁   ▁▁▁▂▂▃▄▆▆█▆▅▃▂▃▃▁▁▁▁▁▁▁▁▁ ▁▁▁  ▁  ▁▁ │
│ dancehall     	│ ▁ ▁▁▁▁▁▂▄▆▆█▇▇▇▆▄▃▂▁▁▁▁▁  ▁  ▁▁▁   ▁ ▁ ▁ │
│ death-metal   	│ ▁▁▂▁▂▃▂▄▃▅▆▇█▇▆▇▅▄▄▃▂▂▁▁▁▂▁▁▁▁▁▁▁▁ ▁▁▁ ▁ │
│ deep-house    	│ ▁▂▃▅▆█▇▅▅▃▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁▁ │
│ detroit-techno	│ ▁▁▁▁▁▁▂▂▂▄▅▃▄▅▄▅▄▅▅▆▇▄█▅▆▇▆▅▅▄▄▃▂▃▂▁▁▁▂▁ │
│ disco         	│ ▁▁  ▁▂▃▅▆█▆▅▆▅▃▂▂▂▂▁▁▁▂▁▁▁▁▁▁▁▁▁ ▁▁▁   ▁ │
│ disney        	│ ▃▆▇▄▄▇▇▆█▅▆▇▅▅▅▄▃▃▃▂▁▂▁▁▁▁▁▁▁▁▁▁▁▁ ▁ ▁ ▁ │
│ drum-and-bass 	│ ▁ ▁▁ ▁ ▁▂▂▃▅▅▄▅▄█▅▄▄▄▃▃▂▂▁▁▁▁▁▁▁ ▁▁▁▁▁ ▁ │
│ dub           	│ ▁▁▁ ▁ ▁▂▂▂▄▃▅█▆▇▇▇▅▄▃▃▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁ ▁▁ │
│ dubstep       	│ ▁▁▁▁ ▁▁▁▂▁▃▄▄▅█▆▇▇▆▅▃▃▂▂▂▁▁▁▁▁▁▁▁ ▁▁▁  ▁ │
│ edm           	│ ▁  ▁▁▁▂▂▄▅ ▇█▅▆▆▄▂▂▂ ▁▁▁▁▁▁▁▁▁  ▁▁▁   ▁▁ │
│ electro       	│ ▁▁▁▁▂▄▄▆▇▆ ▄█▃▃▂▂▁▁▁ ▁▁▁▁ ▁▁▁▁     	▁  │
│ electronic    	│ ▁▁▁▁▂▃▄▆█▇▇▆▆▄▄▃▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁ │
│ emo           	│ ▁▂▂▃▄▅▆█▇▇▇▇█▅▅▂▂▂▂▂▁▁▁ ▁▁▁▁▁ ▁  ▁ 	▁  │
│ folk          	│ ▁ ▁▁▁▁▂▃▃▅▇█▇▅▆▄▅▄▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁  ▁ │
│ forro         	│ ▁▁▁▁▂▃▅▆▆█▇▅▇▄▅▃▃▃▂▁▁▁▁▁▁▁▁ ▁▁ ▁▁	▁ ▁    │
│ french        	│ ▁▁▁▁▂▃▅▅█▇▇▆▅▃▃▂▂▁▁▂▁▁▁ ▁▁▁ ▁▁ ▁   ▁   ▁ │
│ funk          	│ ▁ ▁▁▂▂▅▇█▆▅▆▄▃▄▃▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁▁ │
│ garage        	│ ▁▁▁▁▂▃▃▄▄▅█▆▅▆▅▄▃▂▂▁▁▁▁▁▁▁ ▁▁▁▁▁▁  ▁   ▁ │
│ german        	│ ▁▂▂▁▂▄▆▆▇█▆▅▆▄▃▂▁▂▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ │
│ gospel        	│ ▁ ▁▁▁▁▁▁▂▂▃▃▄▅█▇▅▅▄▃▃▂▂▂▂▂▁▁▂▁▁▁▁▁ ▁▁▁▁▁ │
│ goth          	│ ▁▁ ▁▁▂▂▄▄▅█▅▇▇▅▅▅▅▅▃▃▂▂▁▁▂▁▁▁▁▁ ▁▁  ▁▁▁▁ │
│ grindcore     	│ █▇▆▄▄▄▄▃▃▄▃▃▃▂▂▃▂▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁   ▁  ▁ │
│ groove        	│ ▁ ▁▁▁▂▄▆▆█▆▆▆▅▅▅▃▄▃▂▂▂▁▂▂▁▁▁▁▁▁▁▁▁▁▁▁  ▁ │
│ grunge        	│ ▁▁▁▁▁▁▂▂▄▄▆▇█▅▅▅▃▂▂▁▁▁▁▁▁▁▁▁▁▁  ▁ ▁▁   ▁ │
│ guitar        	│ ▁▁▂▂▃▄▆▅▇▇█▇▇▄▃▃▃▃▃▂▁▂▂▁▂▁▁▁▁▁ ▁▁  ▁▁ ▁▁ │
│ happy         	│ ▁▁▁▁▁▂▂▃▅█▇▇▇▅▄▃▃▃▄▃▃▂▂▂▁▁▁▁▁▁ ▁  ▁▁   ▁ │
│ hard-rock     	│ ▁▂▁▂▂▂▄▇▆▆█▇▇▆▇▇▃▄▄▃▂▃▂▂▁▂▁▁▁▁▁▁▁▁ ▁▁▁▁▁ │
│ hardcore      	│ ▁▂▂▂▃▄▃▄▆▅▆▇█▇▆▄▄▃▃▂▂▁▁ ▁▁▁  ▁  ▁  	▁  │
│ hardstyle     	│ ▁ ▁▁▁▁▁▂▄▆▇█▅▆▅▅▃▂▃▂▂▁▁▁▁▂▁▁▁▁▁▁▁▁▁▁▁ ▁▁ │
│ heavy-metal   	│ ▁▁▁▁▁▁▂▂▃▄▅▆▆▆█▅▅▄▄▃▃▂▂▁▁▁▁▁▁▁▁▁▁▁  ▁  ▁ │
│ hip-hop       	│ ▁▁▁▁▁▁▁▃▃▆▅█▇▆▅▄▃▃▂▂▁▁▁▁▁▁▁▁▁▁▁ ▁	▁▁▁    │
│ honky-tonk    	│ ▁▁▁▁▁ ▂▂▄▅ ▇█▆▅ ▃▂▁▁ ▁▁▁▁ ▁▁	▁  	▁      │
│ house         	│ ▁ ▁▂▃▅▇▇ █▆▇▆▄▂▂ ▂▁▁▁▁▁▁  ▁▁▁▁▁▁ 	▁ ▁    │
│ idm           	│ ▂▁▂▂▂▂▂▄▄▅▆▆▅▆█▇▅▆▄▄▄▄▃▂▃▂▂▁▂▁▁▁▂▁▁▁▁▁ ▁ │
│ indian        	│ ▁▁▁▁▁▁▂▃▄▅▆▆▇█▆▆▅▆▆▅▄▃▂▂▁▂▁▁▁▁▁▁  ▁  ▁ ▁ │
│ indie         	│ ▁▁▁▁▁▁▃▃▅▅▅▆█▅▇▅▅▅▃▃▃▁▃▂▁▁▁▁▁ ▁▁▁▁ ▁   ▁ │
│ indie-pop     	│ ▁ ▁▁▁▄▃▄▅█▇▅▆▆▄▃▃▂▂▁▁▁▁▁ ▁▁▁▁▁▁▁ ▁ 	▁  │
│ industrial    	│ ▁▁▁▁▁▂▂▂▄▃▆█▆▆▅▅▄▄▄▃▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁  ▁ ▁ │
│ iranian       	│ ▄▂▂▂▂▂▂▃▃▅▆▅▆▆▆▇▅▇█▆▇▅▅▅▄▄▅▃▂▂▃▁▂▂▁▂▁▁▂▁ │
│ j-dance       	│ ▁▁ ▁▁▁▁▁▂▄▅▆█▇▆▅▄▂▃▁▁▁▁▁▁▁▁▁▁▁▁  ▁  ▁  ▁ │
│ j-idol        	│ ▁▁▁ ▁▁▁▁▂▂▃▅▅▇█▆▆▄▄▂▂▁▁▁▁▁   ▁     	▁  │
│ j-pop         	│ ▁▁▁▁▁▁▂▂▂▄▆▆▆▆▇▆█▄▃▂▂▂▂▁▁▁▁ ▁▁ ▁▁  ▁   ▁ │
│ j-rock        	│ ▁▂ ▁▁▁▂▂▄▅▆▇█▇▇▅▄▃▂▂▃▁▁▁▁▁▁▁▁  ▁  ▁▁▁  ▁ │
│ jazz          	│ ▁▂ ▁▆▄▆▄▆█▅▄▃▃▂▂▁▁▁▁▁▁▁▁▁ ▁▁▁  ▁▁	▁▁▁    │
│ k-pop         	│ ▁▁▁▁▁▁▁▂▅▆▆█▅▇▆▄▄▃▄▃▃▂▂▁▁▁▁▁▁▁▁▁▁ ▁▁▁ ▁▁ │
│ kids          	│ ▄▄▄▅█▆▆▇▆▆▆▅▄▃▂▁▁▁▁▁ ▁▁ ▁▁▁     	▁  ▁   │
│ latin         	│ ▁	▁▂▁ ▂▃▅▄ ▇▆█ ▆▆▆▃ ▄▂▂ ▁▁▁▁ ▁▁	▁▁     │
│ latino        	│ ▁▁▁▂▂ ▁▂▄▅▄ █▆▆▆▅ ▅▃▃▂ ▂▁▁▁▁ ▁▁ ▁   ▁ ▁▁ │
│ malay         	│ ▁▁▁▁▁▂▃▃▄▅▇▆▇▆▇█▇▅▄▃▃▃▂▁▁▁▁▁▁▁▁▁▁▁  ▁▁▁▁ │
│ mandopop      	│ ▁   ▁ ▁▁▁▁▂▂▂▄▅▇▆█▇▅▄▂▂▂▁▁▁▁▁▁ ▁ ▁ 	▁  │
│ metal         	│ ▁▁▁▁▁▁▁▂▃▄▅█▆▆▄▃▃▃▂▃▁▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁ │
│ metalcore     	│ ▁▁▁▁▁▁▂▂▄▆▇▇█▅▄▃▃▂▁▁▁▁▁▁▁▁▁  ▁ ▁ ▁▁	▁  │
│ minimal-techno	│ ▁  ▁▁▂▂▂▂▂▄▇▄▄▄▄▅▄▄▅▄▆▆█▇▆▆▅▆▅▄▄▅▂▂▂▂▁▁▁ │
│ mpb           	│ ▁▁▁▁▁▁▂▃▄▅█▇▇▆▄▄▃▃▂▂▁▁▁▁▁▁▁▁▁ ▁ ▁	▁▁▁    │
│ new-age       	│ ▁▁▁▁▂▂▄▄▄▇▇▆█▇▇▅▄▆▄▄▄▃▃▃▃▂▂▂▂▁▂▁▁▁▂▁▁▁▁▁ │
│ opera         	│ ▁▁▂▂▂▂▃▄▄▆▆█▆▇▆▅▃▃▂▂▁▁▁▁▁▁▁▁▁▁ ▁  ▁ ▁▁▁▁ │
│ pagode        	│ ▁▁▁▁▂▄▅▆▇█▆▅▃▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁ ▁▁▁▁  	▁  │
│ party         	│ ▁▁ ▁▁▁▁▂▃▄▅█▆▆▆▃▁▁▁▁▁  ▁  ▁	▁   	▁  │
│ piano         	│ ▁▁▁▂▄▄█▆▇▅▇▆▆▇▅▃▂▃▂▂▁▁▁▁▁▁▁▁▁ ▁▁   	▁  │
│ pop           	│ ▁▁▁▁▁▁▂▄█▇▇▅▆▅▄▃▂▂▂▁▁▁▁▁▁▁▁   ▁ ▁  	▁  │
│ pop-film      	│ ▁▁▁▁▁▁▁▁▂▃▄▅▅▆▇▇▇█▇▆▇▄▃▃▂▁▁▁▁▁▁▁ ▁▁ ▁  ▁ │
│ power-pop     	│ ▁▂▂▃▃▄▆▅▆▇▆█▇▅▆▆▄▄▄▃▂▁▁▁▁▁▁▁▁▁▁▁▁ ▁▁  ▁▁ │
│ progressive-house │ ▁▁▁▁▂▅▆█▇▇▇▄▂▂▁▂▁▂▁▁▁▁▁▁▁▁▁▁▁▁   ▁▁ ▁▁ ▁ │
│ psych-rock    	│ ▁▁▁▄▃▄▅█▆▄▅▄▃▃▃▃▁▁▂▂▁▁▁▁▁▁▁▁▁ ▁ ▁▁▁▁▁▁▁▁ │
│ punk          	│ ▁▁▁▁▃▂▃▅▆▆▇█▅▃▃▃▂▂▁▁▁▁▁▁▁▁▁▁  ▁▁ ▁  ▁  ▁ │
│ punk-rock     	│ ▁▁▁▁▂▂▃▄▆▆▇█▅▄▄▃▂▁▁▁▁▁ ▁▁▁▁▁▁ ▁  ▁▁ ▁  ▁ │
│ r-n-b         	│ ▁ ▁▂▃▃█▅▆▇▇▅▄▄▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁  ▁ ▁ ▁▁▁ │
│ reggae        	│ ▂▁▂▃▄▅▇▇█▅▆▅▃ ▃▂▂▁▁▁▁▁▁▁ ▁ ▁▁ ▁▁▁  	▁  │
│ reggaeton     	│ ▁   ▁▁▂▃ ▄▅█▇▇▅▅ ▄▃▃▁▂▁▁ ▁▁▁▁▁▁   ▁ ▁▁ ▁ │
│ rock          	│ ▁▁▁▄▁▂▂▄▃▅▆▆▆█▅▄▃▂▂▂▁▁▁▁▁▁▁▁▁ ▁  ▁▁  ▁ ▁ │
│ rock-n-roll   	│ ▁▁▁▂▄▅█▅▃▃▂▂▂▁▁▁▁▁ ▁▁▁▁ ▁          	▁  │
│ rockabilly    	│ ▁▁▁▁ ▅▅▅ ▅█▄ ▃▃▃ ▂▂▂ ▂▁▂ ▁▁▁ ▁▁  ▁▁  ▁▁▁ │
│ romance       	│ ▁▁▁▁▂▂▄▄▇█▇▇▆▄▃▄▃▂▁▂▁▁▁▁▁▁▁▁ ▁ ▁ ▁ ▁  ▁▁ │
│ sad           	│ ▁▁ ▁▂ ▄▃ ▆ ▇▇ █▆ ▅ ▅▄ ▃▂ ▂ ▁▁ ▁▁ ▁ ▁▁ ▁▁ │
│ salsa         	│ ▁ ▁▂▂▂▂▃▃▃▅▅▆█▇▇▅▃▂▂▂▁▁▁▁▁▁▁ ▁▁▁ ▁ ▁ ▁ ▁ │
│ samba         	│ ▁▁▂▂▂▂▂▃▅▄▇▆▆█▅▅▄▃▃▂▂▁▁▁▁▁▁▁▁▁  ▁▁  ▁  ▁ │
│ sertanejo     	│ ▁▁▃▅▇▇█▅▄▄▄▂▁▁▂▁▁▁▁▁▁▁▁▁▁          	▁  │
│ show-tunes    	│ ▂▂▃▃▃▄▄▆▆▄▆█▄▅▄▃▂▂▂▂▁▂▁▁▁▁▁▁▁▁ ▁▁▁▁ ▁▁▁▁ │
│ singer-songwriter │ ▁▁▁▁▁▁▂▃▄█▄▅▆▅▅▄▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁  ▁▁ ▁ │
│ ska           	│ ▁▁▁▁▂▂▃▄▅▆▇█▇▅▆▃▃▃▂▂▂▁▁▁▁▁▁ ▁▁▁ ▁ ▁   ▁▁ │
│ sleep         	│ ▄▅▅▃▇▆▄▅▆█▅▄▃▃▂▁▁▁▁▁▁ ▁▁ ▁▁ ▁   ▁ ▁  ▁▁▁ │
│ songwriter    	│ ▁▁▁▁▁▁▂▃▄█▄▅▆▅▅▄▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁ ▁  ▁▁ ▁ │
│ soul          	│ ▁▁ ▁▂▅▆▇█▅▄▄▃▃▂▂▁▁▁▁▁▁▁▁▁▁▁▁  ▁    	▁  │
│ spanish       	│ ▁▁ ▁▁▂▄▄▅█▇▇▆▅▄▂▂▂▂▁▁▁▁▁▁ ▁▁ ▁ ▁ ▁   ▁ ▁ │
│ study         	│ ▁▁ ▂▃ ▄ ▅▇ █ ▆▅ ▃ ▂▂ ▁ ▁▁ ▁ ▁▁ ▁ ▁▁ ▁ ▁▁ │
│ swedish       	│ ▁▁▁ ▁▁▂▂▄▆█▅▅▄▄▂▂▂▂▁▁▁▁▁▁▁▁▁▁ ▁▁▁  	▁  │
│ synth-pop     	│ ▁▁▁▁▁▁▁▂▄▄▅▇█▇▇▅▃▃▃▂▂▂▁▁▁▁▁▁▁ ▁▁▁▁▁▁▁▁ ▁ │
│ tango         	│ ▁▁▁▁▃▅▆█▇▆▅▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁  ▁  ▁ ▁ ▁   ▁ │
│ techno        	│ ▁▁▁▁▁▁▂▃▄▅█▇▆▄▃▃▃▃▃▃▃▃▃▄▄▄▃▄▂▃▂▂▂▁▁▁▁▁ ▁ │
│ trance        	│ ▁▁▁▁▄▃▅▅▅█▇▆▅▃▃▂▃▂▂▃▂▂▂▃▂▃▂▂▂▂▂▃▂▁▁▁▁▁▁▁ │
│ trip-hop      	│ ▁▁ ▁▁▁▁▁▃▄▇▇█▇▆▆▅▆▅▄▄▃▃▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁ │
│ turkish       	│ ▁▁▁▁▁▁▃▃▃▄▆█▇▇▅▆▅▅▅▄▂▂▂▂▁▁▁▁▁▁ ▁▁▁▁ ▁  ▁ │
│ world-music   	│ ▁▁▁▁▂▁▁▁▂▂▅▅▇█▅▇▅▆▄▄▃▃▂▃▂▂▂▂▁▂▁▁▁▁▁▁▁▁▁▁ │
└───────────────────┴──────────────────────────────────────────┘

114 rows in set. Elapsed: 0.836 sec. Processed 39.00 thousand rows, 4.51 MB (46.64 thousand rows/s., 5.39 MB/s.)

我们将其留给读者来提取关于他们最喜欢的流派的见解。正如您可能预期的那样,儿童(kids/children)通常很短,但与有趣的 grindcore 流派的共同点比您最初想象的要多!

统计函数

在基本了解我们数据的属性和分布后,我们现在可以使用 ClickHouse 执行更深入的统计分析。ClickHouse 支持其他分析函数以简化查询,否则这些查询可能会非常复杂。我们在下面探讨其中的一些功能。

相关性

了解数据集中的列如何相关是任何统计分析的第一步,通过协助特征选择等任务,为后续的机器学习奠定基础。

我们在 ClickHouse 中有许多相关函数来协助完成此操作。经典地,相关矩阵是理解数据中线性关系的一个不错的初步尝试。corrMatrix 函数允许简洁地实现这一点

SELECT corrMatrix(tempo, danceability, energy, loudness, speechiness, acousticness, instrumentalness, liveness)
FROM spotify

┌─corrMatrix(tempo, danceability, energy, loudness, speechiness, acousticness, instrumentalness, liveness)───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ [[1,-0.05044987143124536,0.24785050980263046,0.21244589657950352,0.017273223330482177,-0.20822414719632454,-0.05033040132787979,0.0005997673112923729],[-0.05044987143124536,1,0.1343254834689951,0.2590767331737369,0.10862608966979727,-0.17153333095375695,-0.185606249730123,-0.13161685668572762],[0.24785050980263046,0.1343254834689951,1,0.7616899598908039,0.14250886780871763,-0.7339063209686977,-0.18187921111248384,0.18479552229595278],[0.21244589657950352,0.2590767331737369,0.7616899598908039,1,0.060826328125056596,-0.5898026667486788,-0.4334768619919035,0.07689866201094041],[0.017273223330482177,0.10862608966979727,0.14250886780871763,0.060826328125056596,1,-0.0021863357894036767,-0.08961576482389075,0.20521905734843637],[-0.20822414719632454,-0.17153333095375695,-0.7339063209686977,-0.5898026667486788,-0.0021863357894036767,1,0.10402711936289526,-0.020700360822699642],[-0.05033040132787979,-0.185606249730123,-0.18187921111248384,-0.4334768619919035,-0.08961576482389075,0.10402711936289526,1,-0.07989258226234942],[0.0005997673112923729,-0.13161685668572762,0.18479552229595278,0.07689866201094041,0.20521905734843637,-0.020700360822699642,-0.07989258226234942,1]] │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

1 row in set. Elapsed: 0.844 sec. Processed 40.00 thousand rows, 4.53 MB (47.38 thousand rows/s., 5.37 MB/s.)

好吧,不得不承认这很难解释!还需要更多 SQL,使用 ClickHouse 的数组函数,才能获得我们大多数人习惯的漂亮网格。

WITH matrix AS
    (
        SELECT arrayJoin(arrayMap(x -> arrayPushFront(x.2, x.1), arrayZip(['tempo', 'danceability', 'energy', 'loudness', 'speechiness', 'acousticness', 'instrumentalness', 'liveness'], 
        arrayMap(row -> arrayMap(col -> round(col, 3), row),  corrMatrix(tempo, danceability, energy, loudness, speechiness, acousticness, instrumentalness, liveness))::Array(Array(String))))) AS matrix
        FROM spotify
    )
SELECT
    matrix[1] AS ` `,
    matrix[2] AS tempo,
    matrix[3] AS danceability,
    matrix[4] AS energy,
    matrix[5] AS loudness,
    matrix[6] AS speechiness,
    matrix[7] AS acousticness,
    matrix[8] AS instrumentalness,
    matrix[9] AS liveness
FROM matrix
┌─ ────────────────┬─tempo──┬─danceability─┬─energy─┬─loudness─┬─speechiness─┬─acousticness─┬─instrumentalness─┬─liveness─┐
│ tempo            │ 1-0.050.2480.2120.017-0.208-0.050.001    │
│ danceability     │ -0.0510.1340.2590.109-0.172-0.186-0.132   │
│ energy           │ 0.2480.13410.7620.143-0.734-0.1820.185    │
│ loudness         │ 0.2120.2590.76210.061-0.59-0.4330.077    │
│ speechiness      │ 0.0170.1090.1430.0611-0.002-0.090.205    │
│ acousticness     │ -0.208-0.172-0.734-0.59-0.00210.104-0.021   │
│ instrumentalness │ -0.05-0.186-0.182-0.433-0.090.1041-0.08    │
│ liveness         │ 0.001-0.1320.1850.0770.205-0.021-0.081        │
└──────────────────┴────────┴──────────────┴────────┴──────────┴─────────────┴──────────────┴──────────────────┴──────────┘

8 rows in set. Elapsed: 0.714 sec. Processed 2.00 thousand rows, 232.29 KB (2.80 thousand rows/s., 325.17 KB/s.)

也许不足为奇的是,energy 与 loudness 呈正相关!也许更有见地的是 acousticness(“从 0.0 到 1.0 的置信度度量,表示曲目是否为原声;1.0 表示曲目为原声的高度置信度”)和 energy 之间的负相关,这表明 energy 较高的曲目原声性较低。原声曲目也似乎更安静。

虽然相关矩阵很有用,但它们假设值之间存在线性关系 - 即使在我们简单的数据集中也不太可能。例如,popularity 不太可能与上述任何属性呈线性相关,但我们可能会期望存在某种关系。它们也仅适用于数值,而不适用于分类变量。

检验零假设

假设检验是统计学中的一个基本概念,在得出推论和做出决策方面起着至关重要的作用。零假设用于使用样本数据评估声明或研究假设的有效性。

使用总体数据的子集,我们声明所研究的人群没有显着差异。这通常以相等性声明的形式呈现,声明两个或多个组相等,或者变量之间没有关系。

通过统计检验,我们旨在确定样本数据中是否有足够的证据来拒绝零假设,转而支持备择假设 - 请注意,此备择假设未定义。

学生 t 检验

为了检验假设,我们可以使用学生 t 检验。这使我们能够评估我们的零假设,即两个总体的均值相等。

例如,也许我们声明

音乐的露骨性质对其是否适合跳舞没有影响。

更具体地说,

露骨音乐的平均 danceability 与非露骨音乐相同

T 检验假设数据近似正态分布且方差相似。这在较小的结果大小(即 < 30)上尤为重要。但是,如前所示,danceability 确实看起来是正态分布的。我们的方差也相似,对于我们的露骨音乐来说,样本量很大

SELECT explicit, varPop(danceability), count() AS c
FROM spotify
GROUP BY explicit

┌─explicit─┬─varPop(danceability)─┬──────c─┐
│ false0.029643285945200746104253 │
│ true0.0298929249273672169747 │
└──────────┴──────────────────────┴────────┘

2 rows in set. Elapsed: 0.840 sec. Processed 77.00 thousand rows, 8.81 MB (91.69 thousand rows/s., 10.49 MB/s.)

在满足这些属性的情况下,在 ClickHouse 中执行 t 检验很简单

SELECT studentTTest(danceability, explicit)
FROM spotify

┌─studentTTest(danceability, explicit)─┐
│ (-41.67680374902913,0)           	   │
└──────────────────────────────────────┘

1 row in set. Elapsed: 0.841 sec. Processed 2.00 thousand rows, 232.29 KB (2.38 thousand rows/s., 276.13 KB/s.)

这里的关键值是元组中的第二个值,即 p 值。这是在假设零假设为真的情况下,获得与观察结果一样极端或更极端的结果的概率。

在我们的例子中,我们的 p 值实际上为 0。这意味着露骨和非露骨音乐的 danceability 样本均值之间观察到的差异不可能仅通过随机机会发生。这意味着我们可以拒绝零假设,即露骨音乐与 danceability 无关。

Welch t 检验

Welch t 检验提供与标准检验类似的功能,但允许方差不同。使用此检验,我们可以检验假设

音乐的露骨性质对其 valence 没有影响。

Valence 描述了曲目传达的音乐积极性。

这也是近似正态分布的,但方差不同

┌─valence─┬─dist─────────────────────────────────────────────────────────────────────────────┐
│   	0 │ █████████████████████▌                                                       	 │
│ 	  0.1 │ █████████████████████████████████████████████████████████▎                   	 │
│ 	  0.2 │ ████████████████████████████████████████████████████████████████████████████▎	 │
│ 	  0.3 │ █████████████████████████████████████████████████████████████████████████████▎   │
│ 	  0.4 │ ████████████████████████████████████████████████████████████████████████████████ │
│ 	  0.5 │ ████████████████████████████████████████████████████████████████████████████▌	 │
│ 	  0.6 │ ████████████████████████████████████████████████████████████████████████▎    	 │
│ 	  0.7 │ ███████████████████████████████████████████████████████████████████▏         	 │
│  	  0.8 │ ████████████████████████████████████████████████████████▉                    	 │
│ 	  0.9 │ ████████████████████████████████████████████▍                                	 │
│   	1 │ ███████████████▍                                                             	 │
└─────────┴──────────────────────────────────────────────────────────────────────────────────┘
SELECT
	explicit,
	varPop(valence),
	count() AS c
FROM spotify
GROUP BY explicit

┌─explicit─┬──────varPop(valence)─┬──────c─┐
│ false0.06861382619038442104253 │
│ true0.0522526044892161559747 │
└──────────┴──────────────────────┴────────┘

2 rows in set. Elapsed: 0.857 sec. Processed 2.00 thousand rows, 232.29 KB (2.33 thousand rows/s., 270.96 KB/s.)

Welch 的 t 检验在 ClickHouse 中同样简单

SELECT welchTTest(valence, if(explicit, 1, 0))
FROM spotify

┌─welchTTest(valence, if(explicit, 1, 0))──┐
│ (1.2775135699871494,0.20144516672703286) │
└──────────────────────────────────────────┘

1 row in set. Elapsed: 0.839 sec. Processed 40.00 thousand rows, 4.53 MB (47.65 thousand rows/s., 5.40 MB/s.)

在这种情况下,我们未能拒绝假设。因此,我们无法说明露骨性是否与积极性相关。

衡量关联

在统计学中,“关联”一词指的是数据集中两个或多个变量之间的关系或联系,它提供了衡量一个变量的变化如何与另一个变量的变化相关的度量。这对于深入了解数据集中的依赖关系和模式至关重要。

Cramer's V 和 Theil's U 都是关联度量。虽然 Cramer's V 衡量两个分类变量之间的关联,但 Theil's U 衡量分类变量与名义变量或连续变量之间的关联。ClickHouse 通过分析函数支持这两种度量。

分类变量指的是将其值作为类别或组的变量。这些可以是名义变量(没有固有的顺序或排名)或序数变量(与之相关的自然顺序或排名)。在我们的 Spotify 数据集中,列 explicit(露骨歌词(true = 是;false = 否或未知))、track_genreartistskey(曲目所在的调。整数使用标准音高类符号映射到音高)。popularity,值为 0(不流行)到 100(流行),以及 ordinal 也是一个序数变量。

下面我们计算这些变量相对于 popularity 的 Cramer's V

SELECT
	cramersV(popularity, explicit),
	cramersV(popularity, key),
	cramersV(popularity, track_genre),
	cramersV(popularity, artists)
FROM spotify

Row 1:
──────
cramersV(popularity, explicit):	0.1111421067814236
cramersV(popularity, key):     	0.049664681157575566
cramersV(popularity, track_genre): 0.16617136848279976
cramersV(popularity, artists): 	0.6256530277850572

1 row in set. Elapsed: 0.843 sec. Processed 51.15 thousand rows, 5.87 MB (60.65 thousand rows/s., 6.96 MB/s.)

Cramer's V 的范围从 0 到 1,其中 0 表示没有关联,1 表示完全关联。正如您所预料的那样,popularity 显然与 artists 有很强的关联。

我们鼓励用户在此处探索其他可能的关联。ClickHouse 还支持具有偏差校正的 Cramer's V 版本(如果数据不平衡或较小,这是该算法的常见挑战)- cramersVBiasCorrected。这表明我们的关联可能没有我们最初测量的那么强

SELECT
	cramersVBiasCorrected(popularity, explicit),
	cramersVBiasCorrected(popularity, key),
	cramersVBiasCorrected(popularity, track_genre),
	cramersVBiasCorrected(popularity, artists)
FROM spotify
FORMAT Vertical

Row 1:
──────
cramersVBiasCorrected(popularity, explicit):	0.10712361030835567
cramersVBiasCorrected(popularity, key):     	0.03986895101010225
cramersVBiasCorrected(popularity, track_genre): 0.1632331461526432
cramersVBiasCorrected(popularity, artists): 	0.34027056010204915

1 row in set. Elapsed: 0.857 sec. Processed 40.00 thousand rows, 4.53 MB (46.67 thousand rows/s., 5.29 MB/s.)

Theil's U 是一种关联度量,它量化了一个变量提供关于另一个变量的信息量,或者更简单地说,量化了因变量中的多少随机性可以由自变量解释。

下面,我们使用 APPLY 语法计算因变量 artists 和其他连续变量的 Theil's U,以查看是否存在明显的关联。我们将这些连续变量设为序数变量,这是我们当前 Theil's U 实现的要求

SELECT * EXCEPT (`Unnamed: 0`, album_name, track_id, artists, track_name, time_signature, track_genre) APPLY x -> theilsU(artists, round(x * 10))
FROM spotify
FORMAT Vertical

Row 1:
──────
theilsU(artists, round(multiply(popularity, 10))):   	-0.3001786653454836
theilsU(artists, round(multiply(duration_ms, 10))):  	-0.9276605586651611
theilsU(artists, round(multiply(explicit, 10))):     	-0.02444367883018377
theilsU(artists, round(multiply(danceability, 10))): 	-0.12620692012945478
theilsU(artists, round(multiply(energy, 10))):       	-0.15039240344091118
theilsU(artists, round(multiply(key, 10))):          	-0.14364542076020673
theilsU(artists, round(multiply(loudness, 10))):     	-0.377285103342597
theilsU(artists, round(multiply(mode, 10))):         	-0.03448619204892218
theilsU(artists, round(multiply(speechiness, 10))):  	-0.07546345974559064
theilsU(artists, round(multiply(acousticness, 10))): 	-0.14720970877815828
theilsU(artists, round(multiply(instrumentalness, 10))): -0.08060433390539239
theilsU(artists, round(multiply(liveness, 10))):     	-0.08880043710056783
theilsU(artists, round(multiply(valence, 10))):      	-0.14408203139969228
theilsU(artists, round(multiply(tempo, 10))):        	-0.524866311749112

1 row in set. Elapsed: 0.991 sec. Processed 114.00 thousand rows, 13.05 MB (115.01 thousand rows/s., 13.17 MB/s.)

重要的是要注意 Theil's U 是不对称的,这意味着 theilsU(X, Y) 不一定等于 theilsU(Y, X)。上面的一些关联似乎是直观的。我们希望大多数艺术家的音乐节奏相似,并且艺术家会影响曲目的 popularity。令人惊讶的是,艺术家创作的歌曲长度也相似。

使用 UDF 简化

我们之前的示例因 Hugging Face 数据集只有一个 Parquet 文件而得到简化。对于那些包含多个文件的数据集,我们可以列出并使用许多文件作为模式中的后缀选择。例如,我们最初的 blog_authorship_corpus 数据集包含 3 个文件。

https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/train/0000.parquet
https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/train/0001.parquet
https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/validation/0000.parquet

使用 URL 模式,我们可以使用以下模式捕获这 3 个文件

https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/{train/0000,train/0001,validation/0000}.parquet

我们的 url 函数接受此模式,允许我们一次查询多个文件。

SELECT count() FROM url('https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/{train/0000,train/0001,validation/0000}.parquet')

虽然可能,但这对于较大的文件列表来说感觉不切实际。下面,我们将此逻辑封装在用户定义的函数 (UDF) 中,用户只需将数据集名称传递给该函数即可。这依赖于前面提到的 Hugging Face 托管 Parquet 文件的可预测 url 格式。

我们首先生成一个函数,该函数输出特定数据集名称的文件列表。

CREATE OR REPLACE FUNCTION hugging_paths AS dataset -> (
	SELECT arrayMap(x -> (x.1), JSONExtract(json, 'parquet_files', 'Array(Tuple(url String))'))
	FROM url('https://datasets-server.huggingface.co/parquet?dataset=' || dataset, 'JSONAsString')
)

SELECT hugging_paths('blog_authorship_corpus') AS paths FORMAT Vertical

Row 1:
──────
paths: ['https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/train/0000.parquet','https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/train/0001.parquet','https://hugging-face.cn/datasets/blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/validation/0000.parquet']

1 row in set. Elapsed: 1.540 sec.

我们可以使用 字符串函数进一步扩展这一点,以创建一个 UDF,该 UDF 调用上述函数,输出一个捕获数据集所有文件的模式。

CREATE OR REPLACE FUNCTION hf AS dataset -> (
	WITH hugging_paths(dataset) as urls
	SELECT multiIf(length(urls) = 0, '', length(urls) = 1, urls[1], 'https://hugging-face.cn/datasets/{' || arrayStringConcat(arrayMap(x -> replaceRegexpOne(replaceOne(x, 'https://hugging-face.cn/datasets/', ''), '\\.parquet$', ''), urls), ',') || '}.parquet')
)

SELECT hf('blog_authorship_corpus') AS pattern
FORMAT Vertical

Row 1:
──────
pattern: https://huggingface.co/datasets/{blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/train/0000,blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/train/0001,blog_authorship_corpus/resolve/refs%2Fconvert%2Fparquet/blog_authorship_corpus/validation/0000}.parquet

1 row in set. Elapsed: 1.633 sec.

有了这个简单的函数,我们可以通过简单地将其作为 url 函数的参数调用,仅使用数据集名称查询任何 Hugging Face 数据集。

SELECT count() AS c,
	artists
FROM url(hf('maharshipandya/spotify-tracks-dataset'))
GROUP BY artists
ORDER BY c DESC
LIMIT 5

┌───c─┬─artists─────────┐
│ 279 │ The Beatles 	│
│ 271 │ George Jones	│
│ 236 │ Stevie Wonder   │
│ 224 │ Linkin Park 	│
│ 222 │ Ella Fitzgerald │
└─────┴─────────────────┘

5 rows in set. Elapsed: 2.917 sec. Processed 2.00 thousand rows, 232.62 KB (686.25 rows/s., 79.74 KB/s.)

虽然调用 Hugging Face API 和解析响应会给我们的查询增加一些开销,但这对于由文件下载和查询主导的较大数据集的查询来说应该是可以忽略不计的。

使用 url 表引擎可以进一步简化上述操作。下面我们为数据集创建一个表抽象。

CREATE TABLE spotify AS url(hf('maharshipandya/spotify-tracks-dataset'))

SELECT count() AS c, artists
FROM spotify
GROUP BY artists
ORDER BY c DESC
LIMIT 5

┌───c─┬─artists─────────┐
│ 279 │ The Beatles 	│
│ 271 │ George Jones	│
│ 236 │ Stevie Wonder   │
│ 224 │ Linkin Park 	│
│ 222 │ Ella Fitzgerald │
└─────┴─────────────────┘

5 rows in set. Elapsed: 1.367 sec. Processed 114.00 thousand rows, 13.05 MB (83.39 thousand rows/s., 9.55 MB/s.)

使用本地表加速查询

之前的所有查询都依赖于使用 url 函数在每次创新时下载 Parquet 文件。虽然可以通过简单地将文件下载到本地文件系统并使用 file 函数来加速常见查询,从而避免每次查询的 HTTP 开销,但性能仍将受到 Parquet 格式的限制。或者,如果更频繁地查询数据集,用户可能希望在 clickhouse-local 会话中创建一个本地表并插入数据。然后可以直接查询此表,从而显着提高性能。

此表与之前由 url 表引擎驱动的示例不同。在本例中,我们将数据插入到 ClickHouse 自己的内部格式中。之前的示例仍然由 Parquet 文件支持。

定义表时,必须定义引擎。用户在这里有两个主要选择 - MergeTree 或 Memory。前者虽然需要定义排序键,但在大多数查询中将提供最佳性能,并且对于较大的数据集不会受到内存的限制。下面我们将 Spotify 数据集插入到一个表中,并重复我们之前简单的查询。

-allow_nullable_key allows us to use track_genre for key
–- this creates the table and inserts the data in a single query
CREATE TABLE spotify_merge
ENGINE = MergeTree
ORDER BY track_genre
SETTINGS allow_nullable_key = 1 AS
SELECT *
FROM url(hf('maharshipandya/spotify-tracks-dataset'))

0 rows in set. Elapsed: 3.038 sec. Processed 114.00 thousand rows, 13.05 MB (37.52 thousand rows/s., 4.30 MB/s.)

SELECT count() AS c, artists
FROM spotify_merge
GROUP BY artists
ORDER BY c DESC
LIMIT 5

┌───c─┬─artists─────────┐
│ 279 │ The Beatles 	│
│ 271 │ George Jones	│
│ 236 │ Stevie Wonder   │
│ 224 │ Linkin Park 	│
│ 222 │ Ella Fitzgerald │
└─────┴─────────────────┘

5 rows in set. Elapsed: 0.016 sec.

除了比 url 函数快近 100 倍之外,请注意在创建表时,我们的模式是如何从 Parquet 文件中自动推断出来的。通过这种使用模式,用户可以利用 ClickHouse MergeTree 的全部功能,MergeTree 为 PB 级实时分析应用程序提供支持。

我们在上面使用了排序键 track_genre。寻求在较大数据集上获得最佳性能或需要针对特定访问模式进行优化的用户应仔细考虑此键。更多信息请访问 此处

此处选择使用模式取决于用户希望查询 Hugging Face 数据集的频率。对于不频繁的临时查询,我们建议使用 url 函数和 UDF。如果您希望频繁查询,则创建表并插入数据将提供更快的查询速度,并在您探索数据时减少迭代周期。

结论

在本博客文章中,我们探讨了如何使用 clickhouse-local 通过 url 函数直接查询 Hugging Face 数据集。我们对 Spotify 数据集执行了一些简单的查询,并说明了如何在不编写任何代码且仅使用 SQL 的情况下执行基本的统计分析。对于较大的数据集,用户可以利用这些统计测试,并且仍然可以从他们的笔记本电脑或工作站上享受 ClickHouse 的性能。最后,我们提供了一个简单的 UDF,读者可以使用它仅通过名称查询 Hugging Face 数据集,并展示了如何将数据集插入到本地表中以获得最佳性能。

分享此文章

订阅我们的新闻邮件

随时了解功能发布、产品路线图、支持和云产品!
正在加载表单...
关注我们
X imageSlack imageGitHub image
Telegram imageMeetup imageRss image