Pandas 数据类型概览(下篇)

阿凡达2018-07-09 12:23

自定义转换函数

由于该数据转换稍微复杂一些,因此我们可以构建一个自定义函数,将其应用于每个值并转换为适当的数据类型。

对于货币转换(这个特定的数据集),下面是一个我们可以使用的简单函数:

def convert_currency(val):
    """
    Convert the string number value to a float
     - Remove $
     - Remove commas
     - Convert to float type
    """
    new_val = val.replace(',','').replace('$', '')
    return float(new_val)

该代码使用python的字符串函数去掉 $,,然后将该值转换为浮点数。在这个特定情况下,我们可以将值转换为整数,但我选择在这种情况下使用浮点数。

我也怀疑有人会建议我们对货币使用 Decimal 类型。这不是 Pandas 的本地数据类型,所以我故意坚持使用 float 方式。

另外值得注意的是,该函数将数字转换为 python 的 float,但 Pandas 内部将其转换为 float64。正如前面提到的,我建议你允许 Pandas 在确定合适的时候将其转换为特定的大小 floatint。你不需要尝试将其转换为更小或更大的字节大小,除非你真的知道为什么需要那样做。

现在,我们可以使用 Pandas 的 apply 函数将其应用于 2016 列中的所有值。

df['2016'].apply(convert_currency)

0    125000.0
1    920000.0
2     50000.0
3    350000.0
4     15000.0
Name: 2016, dtype: float64

成功!所有的值都显示为 float64,我们可以完成所需要的所有数学计算了。

我确信有经验的读者会问为什么我不使用 lambda 函数?在回答之前,先看下我们可以在一行中使用 lambda 函数完成的操作:

df['2016'].apply(lambda x: x.replace('$', '').replace(',', '')).astype('float')

使用 lambda,我们可以将代码简化为一行,这是非常有效的方法。但我对这种方法有三个主要的意见:

  • 如果你只是在学习 Python / Pandas,或者如果将来会有 Python 新人来维护代码,我认为更长的函数的可读性更好。主要原因是它可以包含注释,也可以分解为若干步骤。lambda 函数对于新手来说更难以掌握。
  • 其次,如果你打算在多个列上重复使用这个函数,复制长长的 lambda 函数并不方便。
  • 最后,使用函数可以在使用 read_csv() 时轻松清洗数据。我将在文章结尾处介绍具体的使用方法。

有些人也可能会争辩说,其他基于 lambda 的方法比自定义函数的性能有所提高。但为了教导新手,我认为函数方法更好。

以下是使用 convert_currency 函数转换两个销售(2016 / 2017)列中数据的完整示例。

df['2016'] = df['2016'].apply(convert_currency)
df['2017'] = df['2017'].apply(convert_currency)

df.dtypes

Customer Number      int64
Customer Name       object
2016               float64
2017               float64
Percent Growth      object
Jan Units           object
Month                int64
Day                  int64
Year                 int64
Active              object
dtype: object

有关使用 lambda 和函数的另一个例子,我们可以看看修复 Percent Growth 列的过程。

使用 lambda

df['Percent Growth'].apply(lambda x: x.replace('%', '')).astype('float') / 100

用自定义函数做同样的事情:

def convert_percent(val):
    """
    Convert the percentage string to an actual floating point percent
    - Remove %
    - Divide by 100 to make decimal
    """
    new_val = val.replace('%', '')
    return float(new_val) / 100

df['Percent Growth'].apply(convert_percent)

两者返回的值相同:

0    0.30
1    0.10
2    0.25
3    0.04
4   -0.15
Name: Percent Growth, dtype: float64

我将介绍的最后一个自定义函数是使用 np.where() 将活动(Active)列转换为布尔值。有很多方法来解决这个特定的问题。np.where() 方法对于很多类型的问题都很有用,所以我选择在这里介绍它。

其基本思想是使用 np.where() 函数将所有 Y 值转换为 True,其他所有值为 False

df["Active"] = np.where(df["Active"] == "Y", True, False)

其结果如下 dataframe:

Customer Number Customer Name 2016 2017 Percent Growth Jan Units Month Day Year Active
0 10002.0 Quest Industries $125,000.00 $162500.00 30.00% 500 1 10 2015 True
1 552278.0 Smith Plumbing $920,000.00 $101,2000.00 10.00% 700 6 15 2014 True
2 23477.0 ACME Industrial $50,000.00 $62500.00 25.00% 125 3 29 2016 True
3 24900.0 Brekke LTD $350,000.00 $490000.00 4.00% 75 10 27 2015 True
4 651029.0 Harbor Co $15,000.00 $12750.00 -15.00% Closed 2 2 2014 False

dtype 被正确地设置为了 bool

df.dtypes

Customer Number    float64
Customer Name       object
2016                object
2017                object
Percent Growth      object
Jan Units           object
Month                int64
Day                  int64
Year                 int64
Active                bool
dtype: object

无论你选择使用 lambda 函数,还是创建一个更标准的 Python 函数,或者是使用其他方法(如 np.where),这些方法都非常灵活,并且可以根据你自己独特的数据需求进行定制。

Pandas 辅助函数

Pandas 在直白的 astype() 函数和复杂的自定义函数之间有一个中间地带。这些辅助函数对于某些数据类型转换非常有用。

如果你顺序读下来,你会注意到我没有对日期列或 Jan Units 列做任何事情。这两种列都可以使用 Pandas 的内置函数(如 pd.to_numeric()pd.to_datetime())进行转换。

Jan Units 转换出现问题的原因是列中包含一个非数字值。如果我们尝试使用 astype(),我们会得到一个错误(如前所述)。pd.to_numeric() 函数可以更优雅地处理这些值:

pd.to_numeric(df['Jan Units'], errors='coerce')

0    500.0
1    700.0
2    125.0
3     75.0
4      NaN
Name: Jan Units, dtype: float64

这里有几个值得注意的地方。首先,该函数轻松地处理了数据并创建了一个 float64 列。 此外,它会用 NaN 值替换无效的 Closed 值,因为我们配置了 errors=coerce。我们可以将 Nan 留在那里,也可以使用 fillna(0) 来用 0 填充:

pd.to_numeric(df['Jan Units'], errors='coerce').fillna(0)

0    500.0
1    700.0
2    125.0
3     75.0
4      0.0
Name: Jan Units, dtype: float64

我最终介绍的转换是将单独的月份、日期和年份列转换为到一个 datetime 类型的列。Pandas 的 pd.to_datetime() 函数 可定制性很好,但默认情况下也十分明智。

pd.to_datetime(df[['Month', 'Day', 'Year']])

0   2015-01-10
1   2014-06-15
2   2016-03-29
3   2015-10-27
4   2014-02-02
dtype: datetime64[ns]

在这种情况下,函数将这些列组合成适当 datateime64 dtype 的新列。

我们需要确保将这些值赋值回 dataframe:

df["Start_Date"] = pd.to_datetime(df[['Month', 'Day', 'Year']])
df["Jan Units"] = pd.to_numeric(df['Jan Units'], errors='coerce').fillna(0)
Customer Number Customer Name 2016 2017 Percent Growth Jan Units Month Day Year Active Start_Date
0 10002 Quest Industries 125000.0 162500.0 0.30 500.0 1 10 2015 True 2015-01-10
1 552278 Smith Plumbing 920000.0 1012000.0 0.10 700.0 6 15 2014 True 2014-06-15
2 23477 ACME Industrial 50000.0 62500.0 0.25 125.0 3 29 2016 True 2016-03-29
3 24900 Brekke LTD 350000.0 490000.0 0.04 75.0 10 27 2015 True 2015-10-27
4 651029 Harbor Co 15000.0 12750.0 -0.15 NaN 2 2 2014 False 2014-02-02

现在数据已正确转换为我们需要的所有类型:

df.dtypes

Customer Number             int64
Customer Name              object
2016                      float64
2017                      float64
Percent Growth            float64
Jan Units                 float64
Month                       int64
Day                         int64
Year                        int64
Active                       bool
Start_Date         datetime64[ns]

Dataframe 已准备好进行分析!

把它们放在一起

在数据采集过程中应该尽早地使用 astype() 和自定义转换函数。如果你有一个打算重复处理的数据文件,并且它总是以相同的格式存储,你可以定义在读取数据时需要应用的 dtypeconverters。将 dtype 视为对数据执行 astype() 很有帮助。converters 参数允许你将函数应用到各种输入列,类似于上面介绍的方法。

需要注意的是,只能使用 dtypeconverter 函数中的一种来应用于指定的列。如果你尝试将两者应用于同一列,则会跳过 dtype

下面是一个简化的例子,它在数据读入 dataframe 时完成几乎所有的转换:

df_2 = pd.read_csv("sales_data_types.csv",
                   dtype={'Customer Number': 'int'},
                   converters={'2016': convert_currency,
                               '2017': convert_currency,
                               'Percent Growth': convert_percent,
                               'Jan Units': lambda x: pd.to_numeric(x, errors='coerce'),
                               'Active': lambda x: np.where(x == "Y", True, False)
                              })

df_2.dtypes

Customer Number      int64
Customer Name       object
2016               float64
2017               float64
Percent Growth     float64
Jan Units          float64
Month                int64
Day                  int64
Year                 int64
Active              object
dtype: object

正如前面提到的,我选择了包含用于转换数据的 lambda 示例和函数示例。唯一无法被应用在这里的函数就是那个用来将 MonthDayYear 三列转换到 datetime 列的函数。不过,这仍是一个强大的可以帮助改进数据处理流程的约定。

总结

探索新数据集的第一步是确保数据类型设置正确。大部分时间 Pandas 都会做出合理推论,但数据集中有很多细微差别,因此知道如何使用 Pandas 中的各种数据转换选项非常重要。如果你有任何其他建议,或者有兴趣探索 category 数据类型,请随时在下面发表评论。

相关阅读:Pandas 数据类型概览(上篇)

本文来自网易实践者社区,经作者徐子航授权发布。