各类数据分析问题

PythonPythonBeginner
立即练习

This tutorial is from open-source community. Access the source code

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

在本次实验中,你将学习使用各种 Python 数据容器,并运用列表、集合和字典推导式。你还将探索 collections 模块,它为数据处理提供了实用的工具。

Python 为数据操作和分析提供了强大的工具。在本次实验中,你将练习使用 Python 的内置数据结构和专用工具来分析不同的数据集。从一个简单的投资组合数据集开始,你将逐步过渡到分析芝加哥交通管理局(Chicago Transit Authority)的公交数据,以提取有价值的信息。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ControlFlowGroup(["Control Flow"]) python(("Python")) -.-> python/DataStructuresGroup(["Data Structures"]) python(("Python")) -.-> python/FileHandlingGroup(["File Handling"]) python(("Python")) -.-> python/PythonStandardLibraryGroup(["Python Standard Library"]) python(("Python")) -.-> python/DataScienceandMachineLearningGroup(["Data Science and Machine Learning"]) python/ControlFlowGroup -.-> python/list_comprehensions("List Comprehensions") python/DataStructuresGroup -.-> python/lists("Lists") python/DataStructuresGroup -.-> python/dictionaries("Dictionaries") python/DataStructuresGroup -.-> python/sets("Sets") python/FileHandlingGroup -.-> python/file_reading_writing("Reading and Writing Files") python/FileHandlingGroup -.-> python/file_operations("File Operations") python/PythonStandardLibraryGroup -.-> python/data_collections("Data Collections") python/DataScienceandMachineLearningGroup -.-> python/data_analysis("Data Analysis") subgraph Lab Skills python/list_comprehensions -.-> lab-132438{{"各类数据分析问题"}} python/lists -.-> lab-132438{{"各类数据分析问题"}} python/dictionaries -.-> lab-132438{{"各类数据分析问题"}} python/sets -.-> lab-132438{{"各类数据分析问题"}} python/file_reading_writing -.-> lab-132438{{"各类数据分析问题"}} python/file_operations -.-> lab-132438{{"各类数据分析问题"}} python/data_collections -.-> lab-132438{{"各类数据分析问题"}} python/data_analysis -.-> lab-132438{{"各类数据分析问题"}} end

处理字典和 CSV 数据

让我们从研究一个关于股票持仓的简单数据集开始。在这一步中,你将学习如何从 CSV 文件中读取数据,并使用字典将其存储为结构化格式。

CSV(逗号分隔值)文件是存储表格数据的常用方式,其中每行代表一行数据,值之间用逗号分隔。Python 中的字典是一种强大的数据结构,允许你存储键值对。通过使用字典,我们可以以更有意义的方式组织 CSV 文件中的数据。

首先,按照以下步骤在 WebIDE 中创建一个新的 Python 文件:

  1. 点击 WebIDE 中的“New File”按钮。
  2. 将文件命名为 readport.py
  3. 将以下代码复制并粘贴到文件中:
## readport.py

import csv

## A function that reads a file into a list of dictionaries
def read_portfolio(filename):
    portfolio = []
    with open(filename) as f:
        rows = csv.reader(f)
        headers = next(rows)   ## Skip the header row
        for row in rows:
            record = {
                'name': row[0],
                'shares': int(row[1]),
                'price': float(row[2])
            }
            portfolio.append(record)
    return portfolio

这段代码定义了一个 read_portfolio 函数,它执行了几个重要的任务:

  1. 它打开由 filename 参数指定的 CSV 文件。open 函数用于访问文件,with 语句确保在我们读取完文件后正确关闭文件。
  2. 它跳过标题行。标题行通常包含 CSV 文件中各列的名称。我们使用 next(rows) 将迭代器移动到下一行,从而跳过标题行。
  3. 对于每一行数据,它创建一个字典。字典的键为 'name'、'shares' 和 'price'。这些键将帮助我们以更直观的方式访问数据。
  4. 它将股票数量转换为整数,将价格转换为浮点数。这很重要,因为从 CSV 文件中读取的数据最初是字符串格式,而我们需要数值进行计算。
  5. 它将每个字典添加到一个名为 portfolio 的列表中。这个列表将包含 CSV 文件中的所有记录。
  6. 最后,它返回完整的字典列表。

现在,让我们为公交数据创建一个文件。创建一个名为 readrides.py 的新文件,内容如下:

## readrides.py

import csv

def read_rides_as_dicts(filename):
    """
    Read the CTA bus data as a list of dictionaries
    """
    records = []
    with open(filename) as f:
        rows = csv.reader(f)
        headers = next(rows)   ## Skip header
        for row in rows:
            route = row[0]
            date = row[1]
            daytype = row[2]
            rides = int(row[3])
            record = {
                'route': route,
                'date': date,
                'daytype': daytype,
                'rides': rides
            }
            records.append(record)
    return records

这个 read_rides_as_dicts 函数的工作方式与 read_portfolio 函数类似。它读取与芝加哥交通管理局(CTA)公交数据相关的 CSV 文件,跳过标题行,为每一行数据创建一个字典,并将这些字典存储在一个列表中。

现在,让我们通过在 WebIDE 中打开一个终端来测试 read_portfolio 函数:

  1. 点击“Terminal”菜单并选择“New Terminal”。
  2. 输入 python3 启动 Python 解释器。
  3. 执行以下命令:
>>> from readport import read_portfolio
>>> portfolio = read_portfolio('/home/labex/project/portfolio.csv')
>>> from pprint import pprint
>>> pprint(portfolio)
[{'name': 'AA', 'price': 32.2, 'shares': 100},
 {'name': 'IBM', 'price': 91.1, 'shares': 50},
 {'name': 'CAT', 'price': 83.44, 'shares': 150},
 {'name': 'MSFT', 'price': 51.23, 'shares': 200},
 {'name': 'GE', 'price': 40.37, 'shares': 95},
 {'name': 'MSFT', 'price': 65.1, 'shares': 50},
 {'name': 'IBM', 'price': 70.44, 'shares': 100}]

这里使用了 pprint 函数(漂亮打印)以更易读的格式显示数据。列表中的每个项都是一个表示一笔股票持仓的字典。字典有以下键:

  • 股票代码 (name):这是用于识别股票的缩写。
  • 持有股票数量 (shares):这表示持有的该股票的股数。
  • 每股购买价格 (price):这是每股的购买价格。

注意,像 'MSFT' 和 'IBM' 这样的股票出现了多次。这些代表了对同一只股票的不同购买,可能是在不同时间和以不同价格进行的。

✨ 查看解决方案并练习

使用列表、集合和字典推导式

Python 推导式是一种非常实用且简洁的方式,用于基于现有的集合创建新的集合。Python 中的集合可以是列表、集合或字典,它们就像容器一样,用于存储不同类型的数据。推导式允许你过滤掉某些数据、以某种方式转换数据,并更高效地组织数据。在这部分,我们将使用投资组合数据来探索这些推导式的工作原理。

首先,你需要打开一个 Python 终端,就像你在上一步中所做的那样。终端打开后,你将逐个输入以下示例。这种实践方法将帮助你理解推导式在实际中的工作方式。

列表推导式

列表推导式是 Python 中的一种特殊语法,用于创建新的列表。它通过对现有集合中的每个元素应用一个表达式来实现这一点。

让我们从一个示例开始。首先,我们将导入一个函数来读取我们的投资组合数据。然后,我们将使用列表推导式从投资组合中过滤出某些持仓。

>>> from readport import read_portfolio
>>> portfolio = read_portfolio('/home/labex/project/portfolio.csv')

## Find all holdings with more than 100 shares
>>> large_holdings = [s for s in portfolio if s['shares'] > 100]
>>> print(large_holdings)
[{'name': 'CAT', 'shares': 150, 'price': 83.44}, {'name': 'MSFT', 'shares': 200, 'price': 51.23}]

在这段代码中,我们首先导入 read_portfolio 函数,并使用它从 CSV 文件中读取投资组合数据。然后,列表推导式 [s for s in portfolio if s['shares'] > 100] 遍历 portfolio 集合中的每个元素 s。只有当该持仓的股票数量大于 100 时,才会将元素 s 包含在新列表 large_holdings 中。

列表推导式还可用于执行计算。以下是一些示例:

## Calculate the total cost of each holding (shares * price)
>>> holding_costs = [s['shares'] * s['price'] for s in portfolio]
>>> print(holding_costs)
[3220.0, 4555.0, 12516.0, 10246.0, 3835.15, 3255.0, 7044.0]

## Calculate the total cost of the entire portfolio
>>> total_portfolio_cost = sum([s['shares'] * s['price'] for s in portfolio])
>>> print(total_portfolio_cost)
44671.15

在第一个示例中,列表推导式 [s['shares'] * s['price'] for s in portfolio] 通过将 portfolio 中每个元素的股票数量乘以价格,计算出每个持仓的总成本。在第二个示例中,我们结合使用 sum 函数和列表推导式来计算整个投资组合的总成本。

集合推导式

集合推导式用于从现有集合创建一个集合。集合是一种只包含唯一值的集合。

让我们看看它在我们的投资组合数据中是如何工作的:

## Find all unique stock names
>>> unique_stocks = {s['name'] for s in portfolio}
>>> print(unique_stocks)
{'MSFT', 'IBM', 'AA', 'GE', 'CAT'}

在这段代码中,集合推导式 {s['name'] for s in portfolio} 遍历 portfolio 中的每个元素 s,并将股票名称 (s['name']) 添加到集合 unique_stocks 中。由于集合只存储唯一值,因此我们最终得到了投资组合中所有不同股票的列表,且没有重复项。

字典推导式

字典推导式通过应用表达式来创建键值对,从而创建一个新的字典。

以下是一个使用字典推导式计算投资组合中每只股票总股数的示例:

## Create a dictionary to count total shares for each stock
>>> totals = {s['name']: 0 for s in portfolio}
>>> for s in portfolio:
...     totals[s['name']] += s['shares']
...
>>> print(totals)
{'AA': 100, 'IBM': 150, 'CAT': 150, 'MSFT': 250, 'GE': 95}

在第一行中,字典推导式 {s['name']: 0 for s in portfolio} 创建了一个字典,其中每个股票名称 (s['name']) 是一个键,每个键的初始值为 0。然后,我们使用一个 for 循环遍历 portfolio 中的每个元素。对于每个元素,我们将股票数量 (s['shares']) 添加到 totals 字典中相应键的值上。

这些推导式非常强大,因为它们允许你仅用几行代码就可以转换和分析数据。它们是你 Python 编程工具包中的一个很棒的工具。

探索 collections 模块

在 Python 中,像列表、字典和集合这样的内置容器非常有用。然而,Python 的 collections 模块更进一步,提供了专门的容器数据类型,扩展了这些内置容器的功能。让我们仔细看看其中一些有用的数据类型。

你将继续在 Python 终端中操作,并按照下面的示例进行学习。

Counter

Counter 类是字典的一个子类。它的主要用途是对可哈希对象进行计数。它提供了一种便捷的方式来统计元素数量,并支持多种操作。

首先,我们需要导入 Counter 类和一个读取投资组合的函数。然后,我们将从 CSV 文件中读取一个投资组合。

>>> from collections import Counter
>>> from readport import read_portfolio
>>> portfolio = read_portfolio('/home/labex/project/portfolio.csv')

现在,我们将创建一个 Counter 对象,按股票名称统计每只股票的股数。

## Create a counter to count shares by stock name
>>> totals = Counter()
>>> for s in portfolio:
...     totals[s['name']] += s['shares']
...
>>> print(totals)
Counter({'MSFT': 250, 'IBM': 150, 'CAT': 150, 'AA': 100, 'GE': 95})

Counter 对象的一个很棒的特性是,它会自动将新键的计数初始化为 0。这意味着在增加计数之前,你不必检查键是否存在,这简化了累加计数的代码。

计数器还带有特殊的方法。例如,most_common() 方法在数据分析中非常有用。

## Get the two stocks with the most shares
>>> most_common_stocks = totals.most_common(2)
>>> print(most_common_stocks)
[('MSFT', 250), ('IBM', 150)]

此外,计数器可以使用算术运算进行组合。

## Create another counter
>>> more = Counter()
>>> more['IBM'] = 75
>>> more['AA'] = 200
>>> more['ACME'] = 30
>>> print(more)
Counter({'AA': 200, 'IBM': 75, 'ACME': 30})

## Add two counters together
>>> combined = totals + more
>>> print(combined)
Counter({'AA': 300, 'MSFT': 250, 'IBM': 225, 'CAT': 150, 'GE': 95, 'ACME': 30})

defaultdict

defaultdict 与普通字典类似,但它有一个独特的特性。它为尚未存在的键提供默认值。这可以简化你的代码,因为在使用键之前,你不再需要检查键是否存在。

>>> from collections import defaultdict

## Group portfolio entries by stock name
>>> byname = defaultdict(list)
>>> for s in portfolio:
...     byname[s['name']].append(s)
...
>>> print(byname['IBM'])
[{'name': 'IBM', 'shares': 50, 'price': 91.1}, {'name': 'IBM', 'shares': 100, 'price': 70.44}]
>>> print(byname['AA'])
[{'name': 'AA', 'shares': 100, 'price': 32.2}]

当你创建 defaultdict(list) 时,它会自动为每个新键创建一个新的空列表。因此,即使键之前不存在,你也可以直接向键的值中追加元素。这消除了检查键是否存在并手动创建空列表的需要。

你还可以使用其他默认工厂函数。例如,你可以使用 intfloat 甚至你自己的自定义函数。

## Use defaultdict with int to count items
>>> word_counts = defaultdict(int)
>>> words = ['apple', 'orange', 'banana', 'apple', 'orange', 'apple']
>>> for word in words:
...     word_counts[word] += 1
...
>>> print(word_counts)
defaultdict(<class 'int'>, {'apple': 3, 'orange': 2, 'banana': 1})

collections 模块中的这些专门容器类型在处理数据时可以使你的代码更加简洁和高效。

芝加哥交通管理局数据的数据分析挑战

既然你已经练习了使用不同的 Python 数据结构和 collections 模块,现在是时候将这些技能应用到实际的数据分析任务中了。在这个实验中,我们将分析芝加哥交通管理局(CTA)的公交乘客数据。这个实际应用将帮助你理解如何使用 Python 从真实世界的数据集中提取有意义的信息。

理解数据

首先,让我们看看我们要处理的交通数据。在你的 Python 终端中,你将运行一些代码来加载数据并了解其基本结构。

>>> import readrides
>>> rows = readrides.read_rides_as_dicts('/home/labex/project/ctabus.csv')
>>> print(len(rows))
## This will show the number of records in the dataset

>>> ## Let's look at the first record to understand the structure
>>> import pprint
>>> pprint.pprint(rows[0])

import readrides 语句导入了一个自定义模块,该模块有一个从 CSV 文件读取数据的函数。readrides.read_rides_as_dicts 函数从指定的 CSV 文件中读取数据,并将每一行转换为一个字典。len(rows) 给出了数据集中记录的总数。通过使用 pprint.pprint(rows[0]) 打印第一条记录,我们可以清楚地看到每条记录的结构。

数据包含不同公交线路的每日乘客记录。每条记录包括:

  • route:公交线路编号
  • date:日期,格式为 "YYYY - MM - DD"
  • daytype:"W" 表示工作日,"A" 表示周六,"U" 表示周日/节假日
  • rides:当天的乘客数量

分析任务

让我们逐个解决这些挑战问题:

问题 1:芝加哥有多少条公交线路?

要回答这个问题,我们需要找出数据集中所有唯一的线路编号。我们将使用集合推导式来完成这个任务。

>>> ## Get all unique route numbers using a set comprehension
>>> unique_routes = {row['route'] for row in rows}
>>> print(len(unique_routes))

集合推导式是一种简洁的创建集合的方式。在这种情况下,我们遍历 rows 列表中的每一行,并提取 route 值。由于集合只存储唯一元素,我们最终得到一个包含所有唯一线路编号的集合。打印这个集合的长度,就得到了唯一公交线路的总数。

我们还可以查看其中一些线路编号:

>>> ## Print a few of the route numbers
>>> print(list(unique_routes)[:10])

在这里,我们将唯一线路的集合转换为列表,然后打印该列表的前 10 个元素。

问题 2:2011 年 2 月 2 日,22 路公交车有多少乘客?

对于这个问题,我们需要过滤数据,找到与给定线路和日期匹配的特定记录。

>>> ## Find rides on route 22 on February 2, 2011
>>> target_date = "2011-02-02"
>>> target_route = "22"
>>>
>>> for row in rows:
...     if row['route'] == target_route and row['date'] == target_date:
...         print(f"Rides on route {target_route} on {target_date}: {row['rides']}")
...         break

我们首先定义 target_datetarget_route 变量。然后,我们遍历 rows 列表中的每一行。对于每一行,我们检查 routedate 是否与我们的目标值匹配。如果找到匹配项,我们打印乘客数量并跳出循环,因为我们已经找到了我们要找的记录。

你可以通过更改 target_datetarget_route 变量来检查任何日期的任何线路。

问题 3:每条公交线路的总乘客数是多少?

让我们使用 Counter 来计算每条线路的总乘客数。Countercollections 模块中的一个字典子类,用于对可哈希对象进行计数。

>>> from collections import Counter
>>>
>>> ## Initialize a counter
>>> total_rides_by_route = Counter()
>>>
>>> ## Sum up rides for each route
>>> for row in rows:
...     total_rides_by_route[row['route']] += row['rides']
...
>>> ## View the top 5 routes by total ridership
>>> for route, rides in total_rides_by_route.most_common(5):
...     print(f"Route {route}: {rides:,} total rides")

我们首先从 collections 模块导入 Counter 类。然后,我们初始化一个名为 total_rides_by_route 的空计数器。当我们遍历 rows 列表中的每一行时,我们将每条线路的乘客数量添加到计数器中。最后,我们使用 most_common(5) 方法获取总乘客数最高的前 5 条线路,并打印结果。

问题 4:哪五条公交线路在 2001 年至 2011 年期间乘客数量增长最多?

这是一个更复杂的任务。我们需要比较每条线路在 2001 年和 2011 年的乘客数量。

>>> ## Create dictionaries to store total annual rides by route
>>> rides_2001 = Counter()
>>> rides_2011 = Counter()
>>>
>>> ## Collect data for each year
>>> for row in rows:
...     if row['date'].startswith('2001-'):
...         rides_2001[row['route']] += row['rides']
...     elif row['date'].startswith('2011-'):
...         rides_2011[row['route']] += row['rides']
...
>>> ## Calculate increases
>>> increases = {}
>>> for route in unique_routes:
...     if route in rides_2001 and route in rides_2011:
...         increase = rides_2011[route] - rides_2001[route]
...         increases[route] = increase
...
>>> ## Find the top 5 routes with the biggest increases
>>> import heapq
>>> top_5_increases = heapq.nlargest(5, increases.items(), key=lambda x: x[1])
>>>
>>> ## Display the results
>>> print("Top 5 routes with the greatest ridership increase from 2001 to 2011:")
>>> for route, increase in top_5_increases:
...     print(f"Route {route}: increased by {increase:,} rides")
...     print(f"  2001 rides: {rides_2001[route]:,}")
...     print(f"  2011 rides: {rides_2011[route]:,}")
...     print()

我们首先创建两个 Counter 对象 rides_2001rides_2011,分别存储 2001 年和 2011 年每条线路的总乘客数。当我们遍历 rows 列表中的每一行时,我们检查日期是否以 '2001 -' 或 '2011 -' 开头,并将乘客数添加到相应的计数器中。

然后,我们创建一个空字典 increases 来存储每条线路的乘客增长数量。我们遍历唯一线路,通过从 2011 年的乘客数中减去 2001 年的乘客数来计算增长数量。

为了找到增长最多的前 5 条线路,我们使用 heapq.nlargest 函数。这个函数接受要返回的元素数量(这里是 5)、可迭代对象(increases.items())和一个键函数(lambda x: x[1]),该键函数指定了如何比较元素。

最后,我们打印结果,显示线路编号、乘客增长数量以及 2001 年和 2011 年的乘客数量。

这个分析确定了哪些公交线路在这十年中乘客数量增长最多,这可能表明人口模式的变化、服务的改善或其他有趣的趋势。

你可以通过多种方式扩展这些分析。例如,你可能想要:

  • 按星期几分析乘客模式
  • 找出乘客数量下降的线路
  • 比较乘客数量的季节性变化

你在这个实验中学到的技术为这种数据探索和分析提供了坚实的基础。

✨ 查看解决方案并练习

总结

在这个实验中,你学习了几种重要的 Python 数据处理技术。这些技术包括将 CSV 数据读取并处理为字典、使用列表、集合和字典推导式进行数据转换,以及利用 collections 模块中的特殊容器类型。你还运用这些技能进行了有意义的数据分析。

这些技术是 Python 数据分析的基础,在各种实际场景中都非常有用。对于 Python 程序员来说,处理、转换数据并从中提取见解的能力至关重要。继续使用你自己的数据集进行练习,以提高你在 Python 数据分析方面的专业技能。