简介
在 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 文件。
此列表由上述 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 的控制台模式。对于 Linux 管理员或希望将 clickhouse-local 集成到脚本中的用户,任何查询都可以通过
--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 路径的重定向。参数 enable_url_encoding = 0
(在 23.7 版中引入)确保对 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 │
│ 1000 │ new-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 考虑了歌曲的舞蹈性,基于 tempo、节奏稳定性、节拍强度和整体规律性等音乐元素的组合。0.0 的值为最不适合跳舞的歌曲,而 1.0 的值为最适合跳舞的歌曲。
可以使用bar 函数轻松构建直方图。下面,我们按舞动性分组,四舍五入到小数点后一位,并绘制计数。这将显示值的分布。
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 ()
来确定每个组的最大计数,从而避免我们需要指定一个常量作为条形函数的上限。
直方图的价值之一是它能够帮助快速直观地确定一个值是否服从正态分布,从而为应用其他统计技术打开了可能性。下面,我们使用相同的查询探索其他一些列。
能量是一个从 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 │ ███████████████████████████████████████▊ │
└────────┴──────────────────────────────────────────────────────────────────────────────────┘
活力衡量的是录音中是否有观众。活力值越高,代表曲目在现场表演的可能性越大。大于 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函数。在这里,我们只需指定所需的桶数量,然后重复使用条形图来绘制。
例如,让我们考虑响度。
响度是指轨道的整体响度,以分贝 (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 │ ██████████████████▏ │
│ -1 │ 2 │ █ │
│ 2 │ 5 │ │
└───────┴───────┴──────────────────────────────────────────────────────────────────────────────────┘
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 秒。我们使用此子查询的结果为每个流派构建一个火花条,包含 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)的长度通常很短,但它与有趣的磨核流派有更多的共同点,这可能比你最初认为的要多!
统计函数
在基本了解数据的属性和分布之后,我们现在可以使用 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.)
好吧,不可否认,这很难解释!需要使用 ClickHouse 的数组函数进行更多 SQL 操作,才能生成我们大多数人习惯使用的漂亮网格。
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.05 │ 0.248 │ 0.212 │ 0.017 │ -0.208 │ -0.05 │ 0.001 │
│ danceability │ -0.05 │ 1 │ 0.134 │ 0.259 │ 0.109 │ -0.172 │ -0.186 │ -0.132 │
│ energy │ 0.248 │ 0.134 │ 1 │ 0.762 │ 0.143 │ -0.734 │ -0.182 │ 0.185 │
│ loudness │ 0.212 │ 0.259 │ 0.762 │ 1 │ 0.061 │ -0.59 │ -0.433 │ 0.077 │
│ speechiness │ 0.017 │ 0.109 │ 0.143 │ 0.061 │ 1 │ -0.002 │ -0.09 │ 0.205 │
│ acousticness │ -0.208 │ -0.172 │ -0.734 │ -0.59 │ -0.002 │ 1 │ 0.104 │ -0.021 │
│ instrumentalness │ -0.05 │ -0.186 │ -0.182 │ -0.433 │ -0.09 │ 0.104 │ 1 │ -0.08 │
│ liveness │ 0.001 │ -0.132 │ 0.185 │ 0.077 │ 0.205 │ -0.021 │ -0.08 │ 1 │
└──────────────────┴────────┴──────────────┴────────┴──────────┴─────────────┴──────────────┴──────────────────┴──────────┘
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.)
也许并不奇怪,能量与响度呈正相关!也许更具洞察力的是,声学性(“从 0.0 到 1.0 的置信度度量,表示曲目是否为声学;1.0 代表高置信度,表明曲目为声学”)与能量呈负相关,这表明能量更高的曲目声学性更低。声学曲目似乎也更安静。
虽然相关性矩阵很有用,但它们假设值之间存在线性关系,即使在我们简单的数据集中也不太可能。例如,受欢迎程度不太可能与上述任何属性呈线性关系,但我们可能会预期存在一些关系。它们也只适用于数值,不适用于分类变量。
检验零假设
假设检验是统计学中的一个基本概念,在推断和决策中起着至关重要的作用。零假设用于评估样本数据对主张或研究假设的有效性。
使用整体数据的子集,我们断言研究的总体中没有显著差异。这通常表示为一个平等的陈述,说明两个或多个组相等,或者变量之间没有关系。
通过统计检验,我们旨在确定样本数据中是否有足够的证据来拒绝零假设,而支持备择假设——请注意,备择假设没有定义。
学生 t 检验
为了检验假设,我们可以使用学生 t 检验。这使我们能够评估我们的零假设,即两个总体的均值相等。
例如,也许我们断言
音乐的显式性质不会影响它是否具有舞蹈性。
更具体地说,
显式音乐的平均舞蹈性与非显式音乐的平均舞蹈性相同。
T 检验假设数据近似正态分布且方差相似。这在较小的结果规模(即 < 30)上尤其重要。然而,如前所述,舞蹈性似乎是正态分布的。我们的方差也很相似,并且显式音乐的样本量很大。
SELECT explicit, varPop(danceability), count() AS c
FROM spotify
GROUP BY explicit
┌─explicit─┬─varPop(danceability)─┬──────c─┐
│ false │ 0.029643285945200746 │ 104253 │
│ true │ 0.029892924927367216 │ 9747 │
└──────────┴──────────────────────┴────────┘
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。这意味着显式和非显式舞蹈性样本均值之间的观察差异不可能仅仅是随机机会造成的。这意味着我们可以拒绝零假设,即显式音乐与舞蹈性无关。
Welch t 检验
Welch t 检验提供了与标准 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─┐
│ false │ 0.06861382619038442 │ 104253 │
│ true │ 0.052252604489216155 │ 9747 │
└──────────┴──────────────────────┴────────┘
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.)
在 ClickHouse 中,Welch 的 t 检验同样简单。
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 的 V 和 Theil 的 U 都是关联度量。虽然Cramer 的 V衡量两个分类变量之间的关联,但Theil 的 U 衡量分类变量与名义变量或连续变量之间的关联。ClickHouse 通过分析函数支持这两种度量。
分类变量指的是将类别或组作为其值的变量。这些变量可以是名义变量(没有固有的顺序或排名)或序数变量(与其相关的自然顺序或排名)。在我们的 Spotify 数据集中,列
explicit
(显式歌词(true = 是;false = 否或未知))、track_genre
、artists
和key
(曲目所在的调。整数使用标准音级符号映射到音高)。popularity
的值为 0(不流行)到 100(流行),并且ordinal
也是一个序数变量。
下面,我们计算这些变量与受欢迎程度的 Cramer 的 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 的 V 的范围从 0 到 1,其中 0 表示没有关联,1 表示完美的关联。正如预期的那样,受欢迎程度与艺术家显然有着密切的关联。
我们鼓励用户在这里探索其他可能的关联。ClickHouse 还支持具有偏差校正的 Cramer 的 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 的 U 是一种关联度量,它量化了一个变量对另一个变量提供的信息量,或者更简单地说,一个因变量中的随机性有多少可以由一个自变量来解释。
下面,我们使用APPLY 语法计算因变量 artists
和其他连续变量的 Theil 的 U,看看是否有明显的关联。我们将这些连续变量设为序数,因为这是我们当前实现的 Theil 的 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 的 U 是不对称的,这意味着 theilsU(X, Y)
不一定等于 theilsU(Y, X)
。上面的一些关联似乎很直观。我们预计大多数艺术家的音乐节奏相似,并且艺术家会影响曲目的受欢迎程度。令人惊讶的是,艺术家也创作了长度相似的歌曲。
使用 UDF 简化
我们之前的示例由于 Hugging Face 数据集只有一个 Parquet 文件而得到了简化。对于那些包含多个文件的 dataset,我们可以将多个文件列出来并在模式中用作后缀选择项。例如,我们的初始 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,输出一个捕获数据集所有文件的模式。
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 的全部功能,它支持 PB 级别的实时分析应用程序。
我们使用排序键
track_genre
在上面。希望在更大的数据集上获得最佳性能,或者需要针对特定访问模式进行优化的用户,应该仔细考虑此键。更多信息 在此。
在这里选择使用模式取决于用户预期查询 Hugging Face 数据集的频率。对于不频繁的、临时的查询,我们推荐使用 url 函数和 UDF。如果您预计会频繁查询,则创建表并插入数据将提供更快的查询,并减少在您探索数据时的迭代周期。
结论
在本博文中,我们探讨了如何使用 clickhouse-local 直接使用 url 函数查询 Hugging Face 数据集。我们对 Spotify 数据集进行了一些简单的查询,并说明了如何在不编写任何代码的情况下仅使用 SQL 来执行基本的统计分析。对于更大的数据集,用户可以利用这些统计测试,并仍然享受 ClickHouse 在其笔记本电脑或工作站上的性能。最后,我们提供了一个简单的 UDF,读者可以使用它仅通过名称查询 Hugging Face 数据集,并展示了如何将数据集插入到本地表中以获得最佳性能。