返回介绍

第36单元 数据转换

发布于 2024-01-28 22:01:16 字数 8203 浏览 0 评论 0 收藏 0

你现在已经准备好一睹pandas的“强劲套装”了,它们是矢量化的算术、逻辑运算以及其他数据转换机制。

算术运算

pandas支持四种算术运算(加法、减法、乘法和除法)和numpy的通用函数(ufunc,参考第25单元)。可以使用相应的运算符和函数来组合具有相同大小和结构的frame、frame列和series,以及相同大小的series。

有了算术运算,我们终于可以来更正alco frame的“Total”列了:

   alco["Total"] = alco.Wine + alco.Spirits + alco.Beer
   alco.head()

➾              Beer  Wine  Spirits  TotalState   YearAlabama 1977 0.99  0.13     0.84   1.961978 0.98  0.12     0.88   1.981979 0.98  0.12     0.84   1.941980 0.96  0.16     0.74   1.861981 1.00  0.19     0.73   1.92

如果要用对数尺度来表示总消耗量,可以使用numpy提供的log10()、log()和许多其他通用函数:

   np.log10(alco.Total).head()

➾ State     YearAlabama  1977  0.2922561978  0.2966651979  0.2878021980  0.2695131981  0.283301Name: Total, dtype: float64

所有算术运算都保留索引。该功能称为数据对齐:两个series相加时,pandas会将一个series中带索引“C”的项添加到另一个series中的同名项目中。如果不存在同名项目,则相加的结果为nan。下面我们来模拟基因工程的做法,从第35单元第3小节的原始DNA片段中去除核苷酸C和T,得出两个DNA片段,然后对两个片段的不同核苷酸计数:

   dna = "AGTCCGCGAATACAGGCTCGGT"
   dna1 = dna.replace("C", "")
   dna2 = dna.replace("T", "")
   dna_as_series1 = pd.Series(list(dna1), name="genes") # 移除所有C
   dna_as_series2 = pd.Series(list(dna2), name="genes") # 移除所有T
   dna_as_series1.value_counts() + dna_as_series2.value_counts()

➾ A    10C   NaNG    14T   NaNName: genes, dtype: float64

在进行数据聚合之前,检查一下数据,看看是否需要进行缺失数据的清理(参考第33单元)。

数据聚合

数据聚合由数据分割、聚合和组合这三个步骤组成。

(1) 在分割步骤中,数据按单个键或多个键分割成块 。

(2) 在聚合函数的应用步骤中,将一个聚合函数(比如sum()或count())应用于每个数据块。

(3) 在组合步骤中,计算结果被合并为一个新的series或frame。

pandas的强大就在于它拥有groupby()函数和大批聚合函数,可以自动执行这里列出的三个步骤,所以我们要做的就是喝杯咖啡、坐享其成。

函数groupby()通过基于一个或多个分类的键值将行分到不同的组中,从而实现对frame的分割。该函数返回一个组生成器,可以在循环中使用(来访问组的内容)或者与聚合函数一起使用。

聚合函数包括count()(返回组中的行数),sum()(返回组中数字行的总和),mean()、median()、std()和var()(每个函数都返回组中所有行相应的统计量度量),min()和max()(返回组中最小和最大的行),prod()(返回组中数字行的乘积),first()和last()(返回组中的第一行和最后一行,该函数仅对有序的frame有意义)。

下面来考察一下我们一直在使用的alco frame,并得出一年中所有州总的酒消费量:

   # 下面按年份(列“Year”)分组
   alco_noidx = alco.reset_index()
   sum_alco = alco_noidx.groupby("Year").sum()
   sum_alco.tail()

➾       Beer   Wine   Spirits   TotalYear2005 63.49  18.06    38.89   120.442006 64.37  18.66    40.15   123.182007 64.67  19.08    40.97   124.722008 64.67  19.41    41.59   125.672009 63.22  19.59    41.81   124.62

如果使用多个列进行分割,则结果具有多个索引,每个列对应一个级别的索引。

还可以在一个for循环中,通过组的迭代访问每个组的内容。在每次迭代中,groupby()函数返回的生成器提供索引条目以及与条目对应的行组(以frame的形式):

for year, year_frame in alco_noidx.groupby("Year"):
   «do_something(year, year_frame)»

有时你可能希望使用一个可计算的属性来实现行的分组,而不是使用现有的某个列或多个列。pandas允许使用字典或series的映射来实现数据聚合。让我们考虑一个将州映射到美国人口普查局定义的地区7的字典:

7www2.census.gov/geo/docs/maps-data/maps/reg_div.txt

   state2reg

➾ {'Idaho': 'West', 'West Virginia': 'South', 'Vermont': 'Northeast', «...»}

现在可以按地区计算酒精的平均消费量了!请记住,字典的操作对象是行索引标签,而不是行的值,因此需要给某些列分配frame索引(至少在分组操作时需要这样处理)。字典的值(同时也是组的名称)成为返回的frame的索引:

   alco2009.groupby(state2reg).mean()

➾               Beer       Wine    SpiritsMidwest   1.324167   0.265000   0.822500Northeast 1.167778   0.542222   0.904444South     1.207500   0.275625   0.699375West      1.249231   0.470769   0.843846

奥卡姆8是英国方济会的一位修道士、经院哲学家和神学家。用他的话说(其实并不是他说的),数据聚合精简了实体,因此对我们是有好处的。相反,离散化将值转换为类别,增加了实体,这对我们是不利的,除非它确实非常有用。

8en.wikipedia.org/wiki/William_of_Ockham

离散化

离散化是指将连续变量转换为离散(分类)变量,通常用于直方图和机器学习(参见第10章)。

函数cut()将作为第一个参数的数组或series分割成半开半闭的区间(即类别)。第二个参数是一个数(代表相同大小的区间的个数),或者是一个区间的边界列表。如果要将序列分割成N个区间,则可以传递N+1个区间边界组成的列表。由cut()函数生成的类别属于序数型数据:可以对它们进行排序和相互比较。

   cats = pd.cut(alco2009['Wine'], 3).head()

➾ StateAlabama       (0.0991, 0.4]Alaska           (0.4, 0.7]Arizona       (0.0991, 0.4]Arkansas      (0.0991, 0.4]California       (0.4, 0.7]Name: Wine, dtype: categoryCategories (3, object): [(0.0991, 0.4] < (0.4, 0.7] < (0.7, 1]]

如果你想制作自己的类别标签,只需要传递另一个可选参数labels(N个标签组成的列表,每个区间对应一个标签)。

   cats = pd.cut(alco2009['Wine'], 3, labels=("Low", "Moderate", "Heavy"'))
   cats.head()

➾ StateAlabama            LowAlaska        ModerateArizona            LowArkansas           LowCalifornia    ModerateName: Wine, dtype: categoryCategories (3, object): [Low < Moderate < Heavy]

如果设置labels=False,则cut()函数只对数据区间进行编号而不做标记,并返回区间的成员信息:

   cats = pd.cut(alco2009['Wine'], 3, labels=False).head()

➾ StateAlabama       0Alaska        1Arizona       0Arkansas      0California    1Name: Wine, dtype: int64

函数qcuts()与函数cuts()类似,不同的是qcuts()使用分位数而不是区间宽度进行分割。可以用它来计算分位数(比如中位数和四分位数)。

   quants = pd.qcut(alco2009['Wine'], 3, labels=("Low", "Moderate", "Heavy"))
   quants.head()

➾ StateAlabama            LowAlaska           HeavyArizona       ModerateArkansas           LowCalifornia       HeavyName: Wine, dtype: categoryCategories (3, object): [Low < Moderate < Heavy]

另一种使用少量可能值(这些值已分过类!)离散化变量的方法是将其分解为一组虚拟指标变量,每个变量对应一个可能的值。

虚拟变量是一个布尔型变量,与其对应的类别变量的值为true,其他所有值为false。在逻辑回归以及其他形式的机器学习中用到了虚拟变量,第10章将讨论相关内容。

函数get_dummies()将数组、series或frame转换为与原始对象拥有相同索引的另一个frame,每个列对应一个虚拟变量。如果对象是一个frame,请使用可选参数columns(需要离散化的列组成的列表)。

回顾在第36单元第2小节给出的州的地区划分,此处给出这种划分的使用指示器的表示法:

   pd.get_dummies(state2reg).sort_index().head()

               Midwest  Northeast  South  WeststateAlabama           0          0      1     0Alaska            0          0      0     1Arizona           0          0      0     1Arkansas          0          0      1     0California        0          0      0     1

因为每个州都只属于一个区域,所以每行中所有值的总和总是等于1,对于任何虚拟变量也是如此。

映射

映射是最一般的数据转换方式。它使用map()函数将任意的单参数函数应用于选中列中的每个元素。单参数函数可以是内置的Python函数、任意导入模块的函数、用户定义的函数或匿名的lambda函数。

举个例子,我们来创建三个字母的州名缩写。

   with_state = alco2009.reset_index()
   abbrevs = with_state["State"].map(lambda x: x[:3].upper())
   abbrevs.head()

➾ 0    ALA1    ALA2    ARI3    ARK4    CALName: State, dtype: object

显然,我们没能创建出唯一的三个字母的缩写,但为了理解map()函数,这个例子还是值得一试的!

与高度优化和并行化的通用函数不同,作为参数传递给map()的函数是由Python解释器执行的,无法对其进行优化。这使得map()函数非常低效,所以应该在没有其他选择时才使用它。

交叉表

交叉表计算组频率,并返回一个frame,其行和列分别对应两个分类变量(因子)的不同值。如果提供可选参数marginins=True,该函数还会计算行和列小计值。

以下代码计算一个州是“葡萄酒州”(葡萄酒消费量高于平均水平)还是“啤酒州”(啤酒消费量高于平均水平)的联合频率:

   wine_state = alco2009["Wine"] > alco2009["Wine"].mean()
   beer_state = alco2009["Beer"] > alco2009["Beer"].mean()
   pd.crosstab(wine_state, beer_state)

➾ Beer   False TrueWineFalse     14   15True      12   10

如果表中数字的差异不是很大(本例中的数字差异就不大!),那么这两个因子可能就是独立的。我们将在第47单元第2小节中回顾这个例子。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文