使用 Apache Spark 和 Python 分析数据

在本文中,你将了解如何使用 Azure 开放数据集和 Apache Spark 执行探索性数据分析。 本文分析纽约市出租车数据集。 数据通过 Azure 开放数据集提供。 此数据集子集包含有关黄色出租车行程的信息:有关每次行程、开始和结束时间、位置、成本和其他令人感兴趣的属性的信息。

本文内容:

  • 下载并准备数据
  • 分析数据
  • 可视化数据

先决条件

下载并准备数据

首先,下载纽约市 (NYC) 出租车数据集并准备数据。

  1. 使用 PySpark 创建笔记本。 有关说明,请参阅创建笔记本

    备注

    由于使用的是 PySpark 内核,因此不需要显式创建任何上下文。 运行第一个代码单元格时,系统会自动创建 Spark 上下文。

  2. 在本文中,你将使用多个不同的库来可视化数据集。 若要执行此分析,请导入以下库:

    import matplotlib.pyplot as plt
    import seaborn as sns
    import pandas as pd
    
  3. 原始数据是 Parquet 格式,因此可以使用 Spark 上下文直接将文件作为数据帧提取到内存中。 使用开放数据集 API 检索数据并创建 Spark 数据帧。 为推断数据类型和架构,我们使用 Spark 数据帧“基于读取的架构”属性。

    from azureml.opendatasets import NycTlcYellow
    
    end_date = parser.parse('2018-06-06')
    start_date = parser.parse('2018-05-01')
    nyc_tlc = NycTlcYellow(start_date=start_date, end_date=end_date)
    nyc_tlc_pd = nyc_tlc.to_pandas_dataframe()
    
    df = spark.createDataFrame(nyc_tlc_pd)
    
  4. 读取数据后,执行一些初始筛选来清理数据集。 可能会删除不需要的列,并添加可提取重要信息的列。 此外,你可以筛选掉数据集内的异常。

    # Filter the dataset 
    from pyspark.sql.functions import *
    
    filtered_df = df.select('vendorID', 'passengerCount', 'tripDistance','paymentType', 'fareAmount', 'tipAmount'\
                                    , date_format('tpepPickupDateTime', 'hh').alias('hour_of_day')\
                                    , dayofweek('tpepPickupDateTime').alias('day_of_week')\
                                    , dayofmonth(col('tpepPickupDateTime')).alias('day_of_month'))\
                                .filter((df.passengerCount > 0)\
                                    & (df.tipAmount >= 0)\
                                    & (df.fareAmount >= 1) & (df.fareAmount <= 250)\
                                    & (df.tripDistance > 0) & (df.tripDistance <= 200))
    
    filtered_df.createOrReplaceTempView("taxi_dataset")
    

分析数据

作为数据分析人员,你可以使用多种工具从数据中提取见解。 本文的这一部分介绍了 Microsoft Fabric 笔记本中提供的一些有用工具。 在此分析中,要了解在所选时间段内让司机获得较多出租车小费的因素。

Apache Spark SQL Magic

首先,通过 Microsoft Fabric 笔记本使用 Apache Spark SQL 和 magic 命令执行探索性数据分析。 获取查询结果后,使用内置 chart options 功能可视化结果。

  1. 在笔记本中,创建一个新单元格并复制以下代码。 使用此查询,可以了解所选时间段内平均小费金额的变化情况。 此查询还将帮助我们确定其他有用的见解,包括每天的最小/最大小费金额和平均车费金额。

    %%sql
    SELECT 
        day_of_month
        , MIN(tipAmount) AS minTipAmount
        , MAX(tipAmount) AS maxTipAmount
        , AVG(tipAmount) AS avgTipAmount
        , AVG(fareAmount) as fareAmount
    FROM taxi_dataset 
    GROUP BY day_of_month
    ORDER BY day_of_month ASC
    
  2. 查询运行完以后,你可以通过切换到图表视图来可视化结果。 此示例通过将 day_of_month 字段指定为键并将 avgTipAmount 指定为值来创建折线图。 做出选择后,请选择“应用”以刷新图表。

可视化数据

除了内置的笔记本图表选项外,你还可以使用常用的开放源代码库来创建自己的可视化效果。 在以下示例中,使用 Seaborn 和 Matplotlib,这二者是数据可视化通常使用的 Python 库。

  1. 为了降低开发的难度和费用,需降低数据集的采样。 使用内置的 Apache Spark 采样功能。 此外,Seaborn 和 Matplotlib 都需要 Pandas 数据帧或 NumPy 数组。 若要获取 Pandas 数据帧,请使用 toPandas() 命令来转换数据帧。

    # To make development easier, faster, and less expensive, downsample for now
    sampled_taxi_df = filtered_df.sample(True, 0.001, seed=1234)
    
    # The charting package needs a Pandas DataFrame or NumPy array to do the conversion
    sampled_taxi_pd_df = sampled_taxi_df.toPandas()
    
  2. 可以了解数据集中小费的分布情况。 使用 Matplotlib 创建一个直方图,以显示小费金额和计数的分布情况。 根据分布情况,可以看到,小费倾向于 10 美元或 10 美元以下的金额。

    # Look at a histogram of tips by count by using Matplotlib
    
    ax1 = sampled_taxi_pd_df['tipAmount'].plot(kind='hist', bins=25, facecolor='lightblue')
    ax1.set_title('Tip amount distribution')
    ax1.set_xlabel('Tip Amount ($)')
    ax1.set_ylabel('Counts')
    plt.suptitle('')
    plt.show()
    

    屏幕截图显示小费金额分布的直方图。

  3. 接下来,尝试了解给定行程的小费与一周中各天的关系。 请使用 Seaborn 创建一个箱线图,用于汇总一周中每一天的趋势。

    # View the distribution of tips by day of week using Seaborn
    ax = sns.boxplot(x="day_of_week", y="tipAmount",data=sampled_taxi_pd_df, showfliers = False)
    ax.set_title('Tip amount distribution per day')
    ax.set_xlabel('Day of Week')
    ax.set_ylabel('Tip Amount ($)')
    plt.show()
    
    

    显示每日小费分布情况的示意图。

  4. 另一个假设是乘客数与出租车小费总金额之间存在正相关的关系。 为了验证此关系,请运行以下代码以生成一个箱线图,以展示按乘客计数的小费分布情况。

    # How many passengers tipped by various amounts 
    ax2 = sampled_taxi_pd_df.boxplot(column=['tipAmount'], by=['passengerCount'])
    ax2.set_title('Tip amount by Passenger count')
    ax2.set_xlabel('Passenger count')
    ax2.set_ylabel('Tip Amount ($)')
    ax2.set_ylim(0,30)
    plt.suptitle('')
    plt.show()
    

    此图显示按乘客数量列出小费金额的盒须图。

  5. 最后,了解车费金额与小费金额之间的关系。 从结果来看,可以看到有些情况下乘客不付小费。 但是,在车费总金额和小费总金额之间存在正相关关系。

    # Look at the relationship between fare and tip amounts
    
    ax = sampled_taxi_pd_df.plot(kind='scatter', x= 'fareAmount', y = 'tipAmount', c='blue', alpha = 0.10, s=2.5*(sampled_taxi_pd_df['passengerCount']))
    ax.set_title('Tip amount by Fare amount')
    ax.set_xlabel('Fare Amount ($)')
    ax.set_ylabel('Tip Amount ($)')
    plt.axis([-2, 80, -2, 20])
    plt.suptitle('')
    plt.show()
    

    小费金额散点图的屏幕截图。