由于该数据转换稍微复杂一些,因此我们可以构建一个自定义函数,将其应用于每个值并转换为适当的数据类型。
对于货币转换(这个特定的数据集),下面是一个我们可以使用的简单函数:
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 在确定合适的时候将其转换为特定的大小 float
或 int
。你不需要尝试将其转换为更小或更大的字节大小,除非你真的知道为什么需要那样做。
现在,我们可以使用 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
,我们可以将代码简化为一行,这是非常有效的方法。但我对这种方法有三个主要的意见:
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 在直白的 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()
和自定义转换函数。如果你有一个打算重复处理的数据文件,并且它总是以相同的格式存储,你可以定义在读取数据时需要应用的 dtype
和 converters
。将 dtype
视为对数据执行 astype()
很有帮助。converters
参数允许你将函数应用到各种输入列,类似于上面介绍的方法。
需要注意的是,只能使用 dtype
或 converter
函数中的一种来应用于指定的列。如果你尝试将两者应用于同一列,则会跳过 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
示例和函数示例。唯一无法被应用在这里的函数就是那个用来将 Month
、Day
和 Year
三列转换到 datetime
列的函数。不过,这仍是一个强大的可以帮助改进数据处理流程的约定。
探索新数据集的第一步是确保数据类型设置正确。大部分时间 Pandas 都会做出合理推论,但数据集中有很多细微差别,因此知道如何使用 Pandas 中的各种数据转换选项非常重要。如果你有任何其他建议,或者有兴趣探索 category
数据类型,请随时在下面发表评论。
相关阅读:Pandas 数据类型概览(上篇)
本文来自网易实践者社区,经作者徐子航授权发布。