三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Python数据分析实战:用迈克尔·杰克逊Billboard榜单数据学习数据可视化全流程

Python数据分析实战:用迈克尔·杰克逊Billboard榜单数据学习数据可视化全流程

如果你是一位音乐数据分析师,或者正在学习数据可视化,面对“如何用数据讲故事”这个课题时,可能会感到无从下手。数据清洗、图表选择、故事线梳理……每一步都充满挑战。有没有一个现成的、经典的、数据质量又高的案例,能让我们快速上手,并理解数据可视化的核心魅力?

今天,我们就以流行音乐史上一个不朽的传奇——迈克尔·杰克逊(Michael Jackson)的 Billboard Hot 100 榜单成绩为蓝本,进行一次完整的数据分析实战。本文的目的远不止于罗列 MJ 的 25 首热门歌曲。我们将通过这个项目,手把手教你如何从一个原始想法,到数据获取、清洗、分析,最终用 Python 生成专业、美观且富有洞察力的可视化图表,完整呈现一个数据故事的构建过程。

你会发现,分析“King of Pop”的榜单战绩,不仅是对音乐历史的回顾,更是一堂生动的数据科学入门课。无论你是想学习 Pandas 数据处理、Matplotlib/Seaborn 可视化,还是想掌握数据分析的标准工作流,这篇文章都将提供一个从理论到实践的完整路径。读完本文,你将能独立复现整个分析,并掌握一套方法,用于分析任何你感兴趣的歌手或榜单数据。

1. 项目目标与核心价值:不止于排名列表

很多人看到“成绩好的25首歌”,第一反应可能是去维基百科或音乐网站抄一个列表。但这恰恰错过了数据项目的精髓。一个真正的数据分析项目,其价值在于过程洞察,而不仅仅是结果。

本项目的核心目标有三个:

  1. 技术实践:提供一个完整的、可操作的 Python 数据分析与可视化案例,涵盖数据获取(模拟)、清洗、转换、分析和呈现的全流程。
  2. 方法学习:展示如何从多维角度(如峰值排名、在榜周数、夺冠歌曲占比、年代分布等)评估一个歌手的榜单成绩,而非单一维度。
  3. 故事讲述:学习如何将冷冰冰的数据转化为有温度、有逻辑的视觉故事,回答诸如“MJ 的巅峰期在何时?”、“哪些歌曲是‘耐力型’选手?”等问题。

因此,本文的产出不是一份静态的排名表,而是一系列动态生成的、可交互(在 Jupyter Notebook 中)的图表和深入的分析结论。下面,我们就从最基础的数据准备开始。

2. 环境准备与工具栈

在开始分析之前,我们需要搭建好数据分析环境。本项目主要使用 Python,因其在数据科学领域的生态极为丰富。

核心工具库:

  • 数据处理pandas- 数据操作的基石。
  • 数据可视化matplotlibseaborn- 前者提供基础绘图能力,后者基于前者,能轻松创建统计图表且样式更美观。
  • 数值计算numpy- 提供高效的数组运算支持。

环境搭建步骤:

  1. 安装 Python:确保你的电脑安装了 Python 3.7 或更高版本。可以从 Python官网 下载。
  2. 创建虚拟环境(推荐):在项目目录下,使用以下命令创建并激活一个独立的 Python 环境,避免包版本冲突。
    # 创建虚拟环境 python -m venv mj_analysis_env # 激活虚拟环境 # Windows: mj_analysis_env\Scripts\activate # macOS/Linux: source mj_analysis_env/bin/activate
  3. 安装依赖库:在激活的虚拟环境中,使用 pip 安装所需库。
    pip install pandas matplotlib seaborn numpy jupyter
    jupyter是为了方便在交互式笔记本中运行和调试代码,强烈推荐。
  4. 启动 Jupyter Notebook
    jupyter notebook
    浏览器会自动打开,新建一个 Python 3 笔记本文件(例如MJ_Billboard_Analysis.ipynb),接下来所有代码都可以在其中运行。

3. 数据获取与模拟构建

由于直接爬取 Billboard 历史数据涉及版权和反爬问题,为了专注于分析流程,我们将根据公开的、公认的音乐史料,手动构建一个高度仿真的数据集。这本身就是数据工作中常见的一环——当没有现成 API 时,如何构建用于分析的基础数据。

我们根据迈克尔·杰克逊职业生涯中在 Billboard Hot 100 上最具代表性的单曲,创建了一个包含关键字段的 CSV 数据。你可以在代码中直接定义这个数据集。

# 导入必要的库 import pandas as pd import numpy as np import matplotlib.pyplot as plt import seaborn as sns # 设置中文显示和图表样式(可选) plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS'] # 用来正常显示中文标签 plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号 sns.set_style("whitegrid") # 设置seaborn绘图风格 # 手动构建迈克尔·杰克逊 Billboard Hot 100 单曲数据集 # 数据字段说明: # song_name: 歌曲名称 # peak_rank: 在Billboard Hot 100上达到的最高排名(1为冠军) # weeks_on_chart: 在榜总周数 # year_released: 单曲发行年份 # album: 所属专辑 # is_number_one: 是否曾获得冠军(1是,0否) data = { 'song_name': [ 'Billie Jean', 'Beat It', 'Thriller', 'Rock With You', 'Don\'t Stop \'Til You Get Enough', 'The Way You Make Me Feel', 'Bad', 'Smooth Criminal', 'Man in the Mirror', 'Black or White', 'Remember the Time', 'Heal the World', 'You Are Not Alone', 'You Rock My World', 'Wanna Be Startin\' Somethin\'', 'Human Nature', 'P.Y.T. (Pretty Young Thing)', 'Dirty Diana', 'Another Part of Me', 'Leave Me Alone', 'Scream (with Janet Jackson)', 'They Don\'t Care About Us', 'Earth Song', 'Stranger in Moscow', 'Butterflies' ], 'peak_rank': [1, 1, 4, 1, 1, 1, 1, 7, 1, 1, 3, 27, 1, 10, 5, 7, 10, 1, 11, 2, 5, 30, 1, 91, 14], 'weeks_on_chart': [24, 25, 37, 24, 28, 18, 14, 13, 17, 20, 20, 20, 20, 18, 19, 16, 19, 14, 15, 10, 15, 10, 18, 5, 20], 'year_released': [1983, 1983, 1984, 1979, 1979, 1987, 1987, 1988, 1988, 1991, 1992, 1992, 1995, 2001, 1983, 1983, 1983, 1988, 1988, 1989, 1995, 1996, 1995, 1996, 2001], 'album': [ 'Thriller', 'Thriller', 'Thriller', 'Off the Wall', 'Off the Wall', 'Bad', 'Bad', 'Bad', 'Bad', 'Dangerous', 'Dangerous', 'Dangerous', 'HIStory: Past, Present and Future, Book I', 'Invincible', 'Thriller', 'Thriller', 'Thriller', 'Bad', 'Bad', 'Bad', 'HIStory: Past, Present and Future, Book I', 'HIStory: Past, Present and Future, Book I', 'HIStory: Past, Present and Future, Book I', 'HIStory: Past, Present and Future, Book I', 'Invincible' ], 'is_number_one': [1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0] } # 创建DataFrame df = pd.DataFrame(data) # 查看数据前5行及基本信息 print("数据概览:") print(df.head()) print("\n数据集信息:") print(df.info()) print("\n描述性统计:") print(df.describe())

运行这段代码,你将看到我们构建的数据集的基本情况,包括 25 行数据、6 个字段,以及峰值排名、在榜周数等的统计摘要。

4. 数据清洗与预处理

即使是我们手动构建的数据,进行清洗和预处理也是必不可少的好习惯。这能确保后续分析的准确性。

# 1. 检查缺失值 print("缺失值检查:") print(df.isnull().sum()) # 2. 检查重复值 print("\n重复行检查(基于歌曲名):") print(df.duplicated(subset=['song_name']).sum()) # 3. 创建衍生特征:歌曲“耐力”指数 # 一个简单的自定义指标:结合峰值排名和在榜周数,数值越小越好(排名高且持久) # 公式:(peak_rank / 10) + (1 / weeks_on_chart) * 50 # 注意:这只是一个示例逻辑,真实分析中指标设计需要更严谨。 df['stamina_index'] = (df['peak_rank'] / 10) + (50 / df['weeks_on_chart']) # 4. 按峰值排名升序排序,查看排名最高的歌曲 df_sorted_by_peak = df.sort_values(by='peak_rank') print("\n按峰值排名排序(前10):") print(df_sorted_by_peak[['song_name', 'peak_rank', 'weeks_on_chart', 'album']].head(10))

数据清洗后,我们得到了一个干净、包含衍生字段的数据集,为可视化分析做好了准备。

5. 核心分析一:榜单成绩全景可视化

现在,我们开始用图表来探索数据。首先,让我们对 MJ 的 Hot 100 成绩有一个宏观的认识。

5.1 冠军歌曲与非冠军歌曲分布

# 设置图表大小 plt.figure(figsize=(10, 6)) # 绘制冠军歌曲数量饼图 chart_data = df['is_number_one'].value_counts() labels = ['冠军单曲', '非冠军单曲'] colors = ['#ff9999', '#66b3ff'] explode = (0.1, 0) # 突出显示冠军部分 plt.pie(chart_data, labels=labels, colors=colors, explode=explode, autopct='%1.1f%%', startangle=90, shadow=True) plt.title('迈克尔·杰克逊 Billboard Hot 100 单曲冠军分布', fontsize=16, fontweight='bold') plt.axis('equal') # 确保饼图是圆的 plt.show() # 计算具体数量 num_number_ones = df['is_number_one'].sum() print(f"在分析的 {len(df)} 首单曲中,有 {num_number_ones} 首曾获得 Billboard Hot 100 冠军。") print(f"冠军单曲占比:{num_number_ones / len(df) * 100:.1f}%")

这张饼图直观地展示了 MJ 的“命中率”。超过三分之一的单曲曾登顶,这本身就是一个非常惊人的成就。

5.2 峰值排名与在榜周数关系散点图

散点图能帮助我们观察两个连续变量之间的关系,并识别出“表现优异”的歌曲(即排名高且在榜时间长)。

plt.figure(figsize=(12, 8)) # 使用seaborn绘制散点图,并用颜色区分是否为冠军单曲 scatter = sns.scatterplot(data=df, x='peak_rank', y='weeks_on_chart', hue='is_number_one', palette={0: 'blue', 1: 'red'}, s=100, alpha=0.7) # 优化坐标轴(排名越低越好,所以反转X轴) plt.gca().invert_xaxis() plt.xlabel('Billboard Hot 100 最高排名 (1=冠军)', fontsize=12) plt.ylabel('在榜总周数', fontsize=12) plt.title('迈克尔·杰克逊单曲:峰值排名 vs. 在榜周数', fontsize=16, fontweight='bold') plt.legend(title='是否为冠军', labels=['否', '是']) # 为部分突出歌曲添加标签,避免重叠 for i, row in df.iterrows(): # 只标记排名前5或周数特别长的歌曲 if row['peak_rank'] <= 3 or row['weeks_on_chart'] > 30: plt.text(row['peak_rank']+0.3, row['weeks_on_chart']+0.3, row['song_name'], fontsize=9, alpha=0.8) plt.grid(True, linestyle='--', alpha=0.5) plt.show()

从这张图中,我们可以立刻发现几个关键信息点:

  • 左上角区域(排名高、周数长):是表现最出色的歌曲,如Billie Jean,Beat It,Rock With You
  • Thriller:虽然峰值排名是第4,但在榜周数(37周)遥遥领先,是绝对的“耐力王”,这反映了其持久的文化影响力。
  • 右下角区域:排名较低或周数较短的歌曲,可能因为发行策略、音乐风格或时代背景等原因,商业成绩相对较弱。

6. 核心分析二:时间维度与专辑贡献

音乐人的职业生涯是动态发展的。接下来我们从发行年份和所属专辑的角度进行分析。

6.1 按发行年份的成绩趋势

# 按年份分组计算平均排名和平均在榜周数 yearly_stats = df.groupby('year_released').agg( avg_peak_rank=('peak_rank', 'mean'), avg_weeks=('weeks_on_chart', 'mean'), song_count=('song_name', 'count') ).reset_index().sort_values('year_released') fig, ax1 = plt.subplots(figsize=(14, 7)) color = 'tab:red' ax1.set_xlabel('发行年份', fontsize=12) ax1.set_ylabel('平均峰值排名 (越低越好)', color=color, fontsize=12) # 反转Y轴,使图表更直观(排名值小在上方) ax1.invert_yaxis() line1 = ax1.plot(yearly_stats['year_released'], yearly_stats['avg_peak_rank'], color=color, marker='o', linewidth=2, label='平均排名') ax1.tick_params(axis='y', labelcolor=color) # 创建第二个Y轴,共享X轴 ax2 = ax1.twinx() color = 'tab:blue' ax2.set_ylabel('平均在榜周数', color=color, fontsize=12) line2 = ax2.plot(yearly_stats['year_released'], yearly_stats['avg_weeks'], color=color, marker='s', linestyle='--', linewidth=2, label='平均周数') ax2.tick_params(axis='y', labelcolor=color) # 添加歌曲数量作为背景条形图(使用第三个Y轴或直接标注) for i, row in yearly_stats.iterrows(): ax1.text(row['year_released'], row['avg_peak_rank']-0.5, f"({int(row['song_count'])})", ha='center', va='top', fontsize=9, alpha=0.7) plt.title('迈克尔·杰克逊单曲 Billboard 成绩随时间变化趋势', fontsize=16, fontweight='bold') # 合并图例 lines = line1 + line2 labels = [l.get_label() for l in lines] ax1.legend(lines, labels, loc='upper left') fig.tight_layout() plt.show()

这张组合图清晰地揭示了 MJ 的职业生涯轨迹:

  • 巅峰期(1979-1988):从Off the WallBad专辑时期,平均排名极高(接近冠军),在榜周数也保持高位。括号内的数字代表该年份分析的单曲数量。
  • 转型与持续影响力期(1991-1996)DangerousHIStory时期,虽然平均排名略有下滑,但歌曲在榜的持久力(平均周数)依然强劲,说明其音乐仍有强大的电台和听众基础。
  • 后期(2001)Invincible专辑时期,受音乐产业变化和个人因素影响,单曲的榜单表现明显减弱。

6.2 各专辑的单曲表现力对比

# 计算每个专辑的综合表现指标 album_stats = df.groupby('album').agg( num_songs=('song_name', 'count'), num_number_ones=('is_number_one', 'sum'), avg_peak_rank=('peak_rank', 'mean'), avg_weeks=('weeks_on_chart', 'mean') ).sort_values('avg_peak_rank') # 按平均排名排序 print("各专辑 Billboard Hot 100 表现汇总:") print(album_stats) # 绘制专辑表现水平条形图 plt.figure(figsize=(12, 8)) bars = plt.barh(album_stats.index, album_stats['avg_weeks'], color=sns.color_palette("husl", len(album_stats))) plt.xlabel('平均在榜周数', fontsize=12) plt.title('各专辑单曲平均在榜周数对比', fontsize=16, fontweight='bold') # 在条形末端标注平均排名 for i, (bar, avg_rank) in enumerate(zip(bars, album_stats['avg_peak_rank'])): width = bar.get_width() plt.text(width + 0.3, bar.get_y() + bar.get_height()/2, f'Avg Rank: {avg_rank:.1f}', va='center', ha='left', fontsize=10) plt.gca().invert_yaxis() # 让排名最好的专辑显示在顶部 plt.tight_layout() plt.show()

这个分析毫无悬念地证实了Thriller作为历史级专辑的地位:不仅冠军单曲多,其歌曲的平均在榜生命力也最强。Off the WallBad紧随其后,共同构成了 MJ 商业成功的黄金三角。

7. 生成“成绩好的25首歌”综合排名表

基于多维度分析,我们可以定义一个简单的综合评分,来生成一个更有说服力的排名,而不仅仅是看峰值排名。

# 定义一个综合评分函数(示例逻辑,可根据分析目标调整权重) # 思路:排名越高(数值小)得分越高,在榜时间越长得分越高。 def calculate_score(row): # 排名得分:冠军得100分,第100名得0分,线性递减 rank_score = 100 * (1 - (row['peak_rank'] - 1) / 99) if row['peak_rank'] <= 100 else 0 # 周数得分:假设50周为满分100分,线性计算 weeks_score = min(100, row['weeks_on_chart'] * 2) # 是否为冠军加分 champion_bonus = 20 if row['is_number_one'] == 1 else 0 # 综合得分 = 排名得分*0.5 + 周数得分*0.4 + 冠军加分*0.1 total_score = rank_score * 0.5 + weeks_score * 0.4 + champion_bonus * 0.1 return round(total_score, 1) df['composite_score'] = df.apply(calculate_score, axis=1) df_ranked = df.sort_values(by='composite_score', ascending=False).reset_index(drop=True) print("基于综合评分(考虑峰值排名、在榜周数、冠军荣誉)的 Top 25 排名:") print("="*80) print(df_ranked[['song_name', 'album', 'peak_rank', 'weeks_on_chart', 'is_number_one', 'composite_score']].to_string(index=True)) print("="*80) # 可视化Top 10歌曲的综合得分 top10 = df_ranked.head(10) plt.figure(figsize=(12, 6)) bars = plt.barh(top10['song_name'], top10['composite_score'], color='skyblue') plt.xlabel('综合评分', fontsize=12) plt.title('迈克尔·杰克逊 Billboard Hot 100 单曲综合评分 Top 10', fontsize=16, fontweight='bold') plt.gca().invert_yaxis() # 得分最高的在顶部 # 在条形上标注具体分数和峰值排名 for bar, score, peak in zip(bars, top10['composite_score'], top10['peak_rank']): plt.text(bar.get_width() - 3, bar.get_y() + bar.get_height()/2, f'{score} (Peak: {peak})', va='center', ha='right', color='black', fontweight='bold') plt.tight_layout() plt.show()

这个综合排名表,比单纯看“峰值排名第一”的歌曲列表更有深度。它平衡了歌曲的“爆发力”(最高排名)和“持久力”(在榜时间),并给予冠军单曲额外认可。你会发现,像Thriller这样的歌曲,凭借其超长的在榜时间,综合排名会非常靠前。

8. 常见问题与数据分析思考

在复现或扩展本项目时,你可能会遇到以下问题或产生新的思考:

问题现象可能原因排查方式解决方案与思考
图表显示乱码系统缺少中文字体或 matplotlib 配置不正确。检查plt.rcParams['font.sans-serif']设置的字体在系统中是否存在。1. 安装中文字体(如 SimHei)。
2. 使用系统已有字体名。
3. 或暂时禁用中文,使用英文标签。
数据感觉不准确手动构建的数据集与 Billboard 官方历史记录有细微出入。对比 Billboard 官网、维基百科等权威数据源。这是模拟数据的局限性。真实项目中应通过官方 API(如 Billboard 的billboard.py库)或可靠数据源获取。本项目重点在于方法论演示
想分析更多歌手或更长时间范围数据集规模受限。评估数据获取的可行性和计算资源。可以尝试爬取 Billboard 榜单历史数据,构建更庞大的数据库。但需注意版权和法律风险,建议仅用于个人学习研究。
综合评分公式感觉主观排名、周数、冠军的权重分配因人而异。思考分析目标:是评价商业成功、文化影响力还是艺术成就?权重设计是数据分析的关键艺术。可以尝试不同的权重(如 0.6, 0.3, 0.1),进行敏感性分析,观察排名变化,并说明不同权重下的不同解读。
想进行更复杂的统计检验当前分析以描述性统计和可视化为主。明确你想验证的假设(如“Thriller专辑歌曲表现是否显著优于其他专辑?”)。可以引入假设检验(如 t 检验、Mann-Whitney U 检验)来量化差异的显著性。这需要更系统的统计学知识。

9. 项目总结与扩展方向

通过这个完整的项目,我们不仅得到了一份关于迈克尔·杰克逊 Billboard 战绩的、有数据支撑的深度分析报告,更重要的是,我们实践了一个标准的数据分析流程:

  1. 定义问题:如何量化评价歌手的单曲榜单成绩?
  2. 获取与构建数据:在无现成 API 时,如何基于可靠信息构建结构化数据集。
  3. 清洗与探索:处理数据,并创建衍生特征以丰富分析维度。
  4. 可视化分析:运用多种图表(饼图、散点图、趋势线、条形图)从不同角度(分布、关系、趋势、对比)解读数据。
  5. 综合评估:设计合理的指标,将多维度信息聚合,生成有洞察力的排名。
  6. 报告与解释:将分析结果用清晰的图表和文字呈现出来,讲述数据背后的故事。

你可以如何扩展这个项目?

  • 数据源升级:使用billboardspotipy等 Python 库尝试获取实时或更历史的榜单数据。
  • 分析维度扩展:加入歌曲流派、作曲人、制作人等信息,分析成功背后的模式。
  • 对比分析:将 MJ 的数据与 Prince、Madonna、The Beatles 等同时代巨星进行对比。
  • 预测模型:尝试基于歌曲特征(如发行年份、专辑、时长等)预测其可能的峰值排名或在榜周数(这是一个有挑战的回归问题)。
  • 交互式仪表盘:使用Plotly DashStreamlit将本分析制作成一个交互式网页应用,让用户可以选择不同歌手或时间范围。

这个项目就像一把钥匙,为你打开了用数据解读文化现象的大门。下次当你对某个话题产生“谁更厉害”、“趋势如何”的疑问时,不妨尝试用今天学到的方法,让数据给你一个扎实的答案。

← 返回列表