
#引言
初次接触 Python 数据分析时,大多数分析师通常会选择并学习 pandas。然而,近年来越来越多的专业人士转向了 Polars,它凭借出色的速度和更高的效率迅速获得了广泛的青睐。
Polars 基于 Rust 构建,能够高效处理那些可能让其他工具变得迟缓的数据处理任务。它的设计理念是追求极致的速度、卓越的内存效率和简便的操作。在这篇为初学者量身定制的文章中,我们将利用一个虚构的咖啡店数据,通过实际分析来深入学习 Polars 的强大功能。听起来很有趣,对吧?那么,让我们开始吧!
#安装 Polars
在深入数据分析之前,我们首先来完成 Polars 的安装步骤。首先,请安装 Polars 及其依赖库:
! pip install polars numpy
接下来,导入所需的库和模块:
import polars as pl
import numpy as np
from datetime import datetime, timedelta
我们通常使用 pl 作为 Polars 的别名,以简化代码。
#创建示例数据
想象一下,您正在经营一家名为“Bean There”的小咖啡馆,手头有数百张收据及相关数据需要分析。您可能想了解哪些饮品最畅销、哪些日子的收入最高,以及其他类似的业务问题。那么,事不宜迟,让我们开始编写代码吧!☕
为了让本指南更具实践性,我们为“Bean There 咖啡馆”创建了一个真实的数据集。我们将生成一份任何小型企业主都会感到熟悉的交易数据:
# Set up for consistent results
np.random.seed(42)
# Create realistic coffee shop data
def generate_coffee_data():
n_records = 2000
# Coffee menu items with realistic prices
menu_items = ['Espresso', 'Cappuccino', 'Latte', 'Americano', 'Mocha', 'Cold Brew']
prices = [2.50, 4.00, 4.50, 3.00, 5.00, 3.50]
price_map = dict(zip(menu_items, prices))
# Generate dates over 6 months
start_date = datetime(2023, 6, 1)
dates = [start_date + timedelta(days=np.random.randint(0, 180))
for _ in range(n_records)]
# Randomly select drinks, then map the correct price for each selected drink
drinks = np.random.choice(menu_items, n_records)
prices_chosen = [price_map[d] for d in drinks]
data = {
'date': dates,
'drink': drinks,
'price': prices_chosen,
'quantity': np.random.choice([1, 1, 1, 2, 2, 3], n_records),
'customer_type': np.random.choice(['Regular', 'New', 'Tourist'],
n_records, p=[0.5, 0.3, 0.2]),
'payment_method': np.random.choice(['Card', 'Cash', 'Mobile'],
n_records, p=[0.6, 0.2, 0.2]),
'rating': np.random.choice([2, 3, 4, 5], n_records, p=[0.1, 0.4, 0.4, 0.1])
}
return data
# Create our coffee shop DataFrame
coffee_data = generate_coffee_data()
df = pl.DataFrame(coffee_data)
这段代码创建了一个包含 2,000 笔咖啡交易的样本数据集。每一行代表一笔销售记录,详细列出了所购买的饮品、交易时间、价格以及顾客类型等信息。
#查看您的数据
在进行任何数据分析之前,您需要先了解正在处理的数据。这就像在烹饪前先仔细阅读食谱一样,是至关重要的第一步:
# Take a peek at your data
print("First 5 transactions:")
print(df.head())
print("
What types of data do we have?")
print(df.schema)
print("
How big is our dataset?")
print(f"We have {df.height} transactions and {df.width} columns")
head() 方法可以显示数据的前几行,让您对数据有一个初步的了解。而 schema 则会告诉您每个列包含的数据类型(例如,数字、文本或日期等),这对于后续的数据处理非常重要。
First 5 transactions:
shape: (5, 7)
┌─────────────────────┬────────────┬───────┬──────────┬───────────────┬────────────────┬────────┐
│ date ┆ drink ┆ price ┆ quantity ┆ customer_type ┆ payment_method ┆ rating │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ datetime[μs] ┆ str ┆ f64 ┆ i64 ┆ str ┆ str ┆ i64 │
╞═════════════════════╪════════════╪═══════╪══════════╪═══════════════╪════════════════╪════════╡
│ 2023-09-11 00:00:00 ┆ Cold Brew ┆ 5.0 ┆ 1 ┆ New ┆ Cash ┆ 4 │
│ 2023-11-27 00:00:00 ┆ Cappuccino ┆ 4.5 ┆ 1 ┆ New ┆ Card ┆ 4 │
│ 2023-09-01 00:00:00 ┆ Espresso ┆ 4.5 ┆ 1 ┆ Regular ┆ Card ┆ 3 │
│ 2023-06-15 00:00:00 ┆ Cappuccino ┆ 5.0 ┆ 1 ┆ New ┆ Card ┆ 4 │
│ 2023-09-15 00:00:00 ┆ Mocha ┆ 5.0 ┆ 2 ┆ Regular ┆ Card ┆ 3 │
└─────────────────────┴────────────┴───────┴──────────┴───────────────┴────────────────┴────────┘
What types of data do we have?
Schema({'date': Datetime(time_unit='us', time_zone=None), 'drink': String, 'price': Float64, 'quantity': Int64, 'customer_type': String, 'payment_method': String, 'rating': Int64})
How big is our dataset?
We have 2000 transactions and 7 columns
#添加新列
现在,让我们开始从数据中提取有价值的商业洞察。每一位咖啡店老板都希望了解每笔交易的总收入:
# Calculate total sales amount and add useful date information
df_enhanced = df.with_columns([
# Calculate revenue per transaction
(pl.col('price') * pl.col('quantity')).alias('total_sale'),
# Extract useful date components
pl.col('date').dt.weekday().alias('day_of_week'),
pl.col('date').dt.month().alias('month'),
pl.col('date').dt.hour().alias('hour_of_day')
])
print("Sample of enhanced data:")
print(df_enhanced.head())
输出结果(您的具体数字可能有所不同):
Sample of enhanced data:
shape: (5, 11)
┌─────────────┬────────────┬───────┬──────────┬───┬────────────┬─────────────┬───────┬─────────────┐
│ date ┆ drink ┆ price ┆ quantity ┆ … ┆ total_sale ┆ day_of_week ┆ month ┆ hour_of_day │
│ --- ┆ --- ┆ f64 ┆ i64 ┆ ┆ f64 ┆ i8 ┆ i8 ┆ i8 │
│ datetime[μs ┆ str ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ ] ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
╞═════════════╪════════════╪═══════╪══════════╪═══╪════════════╪═════════════╪═══════╪═════════════╡
│ 2023-09-11 ┆ Cold Brew ┆ 5.0 ┆ 1 ┆ … ┆ 5.0 ┆ 1 ┆ 9 ┆ 0 │
│ 00:00:00 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2023-11-27 ┆ Cappuccino ┆ 4.5 ┆ 1 ┆ … ┆ 4.5 ┆ 1 ┆ 11 ┆ 0 │
│ 00:00:00 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2023-09-01 ┆ Espresso ┆ 4.5 ┆ 1 ┆ … ┆ 4.5 ┆ 5 ┆ 9 ┆ 0 │
│ 00:00:00 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2023-06-15 ┆ Cappuccino ┆ 5.0 ┆ 1 ┆ … ┆ 5.0 ┆ 4 ┆ 6 ┆ 0 │
│ 00:00:00 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2023-09-15 ┆ Mocha ┆ 5.0 ┆ 2 ┆ … ┆ 10.0 ┆ 5 ┆ 9 ┆ 0 │
│ 00:00:00 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
└─────────────┴────────────┴───────┴──────────┴───┴────────────┴─────────────┴───────┴─────────────┘
这段代码的操作解释如下:
with_columns()方法用于向数据框添加新列。pl.col()用于引用现有的列。alias()作用是为我们创建的新列指定一个描述性的名称。dt访问器则可以从日期时间列中提取特定的组成部分(例如,从完整日期中提取月份)。
您可以将此操作类比为在电子表格中添加计算字段。我们并没有修改原始数据,只是在原有基础上增加了更多可供分析的信息。
#数据分组
现在,让我们来回答一些有趣的业务问题。
//问题 1:哪些饮品是我们的畅销款?
这段代码将所有交易按饮品类型进行分组,然后计算每个组的总销售额和平均评分。这就像是把您的所有收据按饮品种类分堆,然后分别计算每堆的总数一样。
drink_performance = (df_enhanced
.group_by('drink')
.agg([
pl.col('total_sale').sum().alias('total_revenue'),
pl.col('quantity').sum().alias('total_sold'),
pl.col('rating').mean().alias('avg_rating')
])
.sort('total_revenue', descending=True)
)
print("Drink performance ranking:")
print(drink_performance)
输出结果:
Drink performance ranking:
shape: (6, 4)
┌────────────┬───────────────┬────────────┬────────────┐
│ drink ┆ total_revenue ┆ total_sold ┆ avg_rating │
│ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ i64 ┆ f64 │
╞════════════╪═══════════════╪════════════╪════════════╡
│ Americano ┆ 2242.0 ┆ 595 ┆ 3.476454 │
│ Mocha ┆ 2204.0 ┆ 591 ┆ 3.492711 │
│ Espresso ┆ 2119.5 ┆ 570 ┆ 3.514793 │
│ Cold Brew ┆ 2035.5 ┆ 556 ┆ 3.475758 │
│ Cappuccino ┆ 1962.5 ┆ 521 ┆ 3.541139 │
│ Latte ┆ 1949.5 ┆ 514 ┆ 3.528846 │
└────────────┴───────────────┴────────────┴────────────┘
//问题 2:每日销售情况如何?
现在,让我们来查找每周各天的交易数量和相应的收入。
daily_patterns = (df_enhanced
.group_by('day_of_week')
.agg([
pl.col('total_sale').sum().alias('daily_revenue'),
pl.len().alias('number_of_transactions')
])
.sort('day_of_week')
)
print("Daily business patterns:")
print(daily_patterns)
输出结果:
Daily business patterns:
shape: (7, 3)
┌─────────────┬───────────────┬────────────────────────┐
│ day_of_week ┆ daily_revenue ┆ number_of_transactions │
│ --- ┆ --- ┆ --- │
│ i8 ┆ f64 ┆ u32 │
╞═════════════╪═══════════════╪════════════════════════╡
│ 1 ┆ 2061.0 ┆ 324 │
│ 2 ┆ 1761.0 ┆ 276 │
│ 3 ┆ 1710.0 ┆ 278 │
│ 4 ┆ 1784.0 ┆ 288 │
│ 5 ┆ 1651.5 ┆ 265 │
│ 6 ┆ 1596.0 ┆ 259 │
│ 7 ┆ 1949.5 ┆ 310 │
└─────────────┴───────────────┴────────────┴────────────┘
#数据筛选
让我们找出那些高价值的交易:
# Find transactions over $10 (multiple items or expensive drinks)
big_orders = (df_enhanced
.filter(pl.col('total_sale') > 10.0)
.sort('total_sale', descending=True)
)
print(f"We have {big_orders.height} orders over $10")
print("Top 5 biggest orders:")
print(big_orders.head())
输出结果:
We have 204 orders over $10
Top 5 biggest orders:
shape: (5, 11)
┌─────────────┬────────────┬───────┬──────────┬───┬────────────┬─────────────┬───────┬─────────────┐
│ date ┆ drink ┆ price ┆ quantity ┆ … ┆ total_sale ┆ day_of_week ┆ month ┆ hour_of_day │
│ --- ┆ --- ┆ f64 ┆ i64 ┆ ┆ f64 ┆ i8 ┆ i8 ┆ i8 │
│ datetime[μs ┆ str ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ ] ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
╞═════════════╪════════════╪═══════╪══════════╪═══╪════════════╪═════════════╪═══════╪═════════════╡
│ 2023-07-21 ┆ Cappuccino ┆ 5.0 ┆ 3 ┆ … ┆ 15.0 ┆ 5 ┆ 7 ┆ 0 │
│ 00:00:00 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2023-08-02 ┆ Latte ┆ 5.0 ┆ 3 ┆ … ┆ 15.0 ┆ 3 ┆ 8 ┆ 0 │
│ 00:00:00 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2023-07-21 ┆ Cappuccino ┆ 5.0 ┆ 3 ┆ … ┆ 15.0 ┆ 5 ┆ 7 ┆ 0 │
│ 00:00:00 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2023-10-08 ┆ Cappuccino ┆ 5.0 ┆ 3 ┆ … ┆ 15.0 ┆ 7 ┆ 10 ┆ 0 │
│ 00:00:00 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
│ 2023-09-07 ┆ Latte ┆ 5.0 ┆ 3 ┆ … ┆ 15.0 ┆ 4 ┆ 9 ┆ 0 │
│ 00:00:00 ┆ ┆ ┆ ┆ ┆ ┆ ┆ ┆ │
└─────────────┴────────────┴───────┴──────────┴───┴────────────┴─────────────┴───────┴─────────────┘
#分析顾客行为
让我们深入分析顾客的消费模式:
# Analyze customer behavior by type
customer_analysis = (df_enhanced
.group_by('customer_type')
.agg([
pl.col('total_sale').mean().alias('avg_spending'),
pl.col('total_sale').sum().alias('total_revenue'),
pl.len().alias('visit_count'),
pl.col('rating').mean().alias('avg_satisfaction')
])
.with_columns([
# Calculate revenue per visit
(pl.col('total_revenue') / pl.col('visit_count')).alias('revenue_per_visit')
])
)
print("Customer behavior analysis:")
print(customer_analysis)
输出结果:
Customer behavior analysis:
shape: (3, 6)
┌───────────────┬──────────────┬───────────────┬─────────────┬──────────────────┬──────────────────┐
│ customer_type ┆ avg_spending ┆ total_revenue ┆ visit_count ┆ avg_satisfaction ┆ revenue_per_visi │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ t │
│ str ┆ f64 ┆ f64 ┆ u32 ┆ f64 ┆ --- │
│ ┆ ┆ ┆ ┆ ┆ f64 │
╞═══════════════╪══════════════╪═══════════════╪═════════════╪══════════════════╪══════════════════╡
│ Regular ┆ 6.277832 ┆ 6428.5 ┆ 1024 ┆ 3.499023 ┆ 6.277832 │
│ Tourist ┆ 6.185185 ┆ 2505.0 ┆ 405 ┆ 3.518519 ┆ 6.185185 │
│ New ┆ 6.268827 ┆ 3579.5 ┆ 571 ┆ 3.502627 ┆ 6.268827 │
└───────────────┴──────────────┴───────────────┴─────────────┴──────────────────┴──────────────────┘
#综合汇总
现在,让我们来创建一个全面的业务摘要报告:
# Create a complete business summary
business_summary = {
'total_revenue': df_enhanced['total_sale'].sum(),
'total_transactions': df_enhanced.height,
'average_transaction': df_enhanced['total_sale'].mean(),
'best_selling_drink': drink_performance.row(0)[0], # First row, first column
'customer_satisfaction': df_enhanced['rating'].mean()
}
print("
=== BEAN THERE COFFEE SHOP - SUMMARY ===")
for key, value in business_summary.items():
if isinstance(value, float) and key != 'customer_satisfaction':
print(f"{key.replace('_', ' ').title()}: ${value:.2f}")
else:
print(f"{key.replace('_', ' ').title()}: {value}")
输出结果:
=== BEAN THERE COFFEE SHOP - SUMMARY ===
Total Revenue: $12513.00
Total Transactions: 2000
Average Transaction: $6.26
Best Selling Drink: Americano
Customer Satisfaction: 3.504
#结论
恭喜您!您刚刚完成了 Polars 数据分析的全面入门指南!通过我们咖啡店的实际案例,相信您已经掌握了如何将原始交易数据转化为富有洞察力的商业智能。
请记住,精通数据分析就像学习烹饪一样——您需要从基础食谱(如同本指南中的示例)开始,然后逐步提高。实践和好奇心是掌握这项技能的关键。
下次当您分析数据集时,不妨问自己以下问题:
- 这些数据在讲述一个怎样的故事?
- 其中可能隐藏着哪些模式?
- 这些数据能回答哪些关键问题?
然后,运用您新学到的 Polars 技能去找出答案吧。祝您分析愉快!
