Python数据分析入门:四个简单示例带你轻松掌握Pandas

前言

本文选取了四个经典案例,主要聚焦Pandas在数据分析中的简单应用,结合代码学习利用Python进行数据分析过程(●ˇ∀ˇ●)。在每个例题开始前,我们将会标注出本例题涉及的重要知识点,并在重要处添加解释和代码注释,共读者参考。

如果你遇到任何问题,欢迎在评论区一起讨论╰(*°▽°*)╯

点击最上方横幅👆下载本文例子中用到的数据包,或点此👉下载链接

敬告:数据内容仅供学习使用,不代表任何真实数据!请勿作他用!╰(‵□′)╯ 


了解Pandas

Pandas(Python Data Analysis Library)是基于NumPy的数据分析模块,它提供了大量标准数据模型和高效操作大型数据集所需的工具。可以说Pandas是使得Python能够成为高效且强大的数据分析环境的重要因素之一。

pandas' logo

Pandas提供了两种主要的数据结构:Series和DataFrame。这些数据结构使得在Python中进行数据操作和分析变得更加方便。

1. Series: Series是Pandas中的一维标记数组。它类似于一维数组或列表,但附带了标签(label),使得数据可以按标签进行访问和操作。Series由两部分组成:数据部分和索引(index)。数据部分存储了一组值,而索引是用于标识和访问这些值的标签。

例如,创建一个Series对象来表示学生的分数:

import pandas as pd

scores = pd.Series([85, 90, 75, 80])

这将创建一个包含分数数据的Series对象。默认情况下,索引将从0开始自动分配。可以通过指定索引来自定义标签:

scores = pd.Series([85, 90, 75, 80], index=['Alice', 'Bob', 'Charlie', 'Dave'])

现在,每个分数都与对应的学生姓名相关联。

2. DataFrame: DataFrame是Pandas中最常用的数据结构,它类似于一个二维表格或电子表格。它由行和列组成,每一列可以包含不同类型的数据(如数字、字符串、布尔值等)。

可以将DataFrame视为一组Series对象的集合,它们共享相同的索引。DataFrame提供了许多功能,包括数据选择、过滤、排序、统计和数据可视化等。

创建一个简单的DataFrame示例:

data = {'Name': ['Alice', 'Bob', 'Charlie', 'Dave'],
        'Age': [25, 30, 35, 40],
        'City': ['New York', 'London', 'Paris', 'Tokyo']}

df = pd.DataFrame(data)

将创建一个包含姓名、年龄和城市的DataFrame对象。每一列都表示一个Series对象,而DataFrame提供了将它们组合在一起的结构。

(≧∇≦)ノ学习更多关于Pandas的知识,请跳转到:Pandas教程(非常详细)-CSDN博客


1.MoviesLens 1M数据集 

GroupLens实验室提供了一些从MoviesLens用户那里收集的20世纪90年代末到21世纪初的电影评分数据的集合。浙西额数据提供了电影的评分、流派、年份和观众数据(年龄、邮编、性别、职业)。MovisLens1M数据集包含6000个用户对4000部电影的100万个评分。数据分布在三个表格之中:分别包含评分、用户信息和电影信息。


接下来我们将结合代码共同学习这一分析过程。本例题涉及以下知识点,重要处会有解释:

  1. 数据合并和连接:使用pd.merge()函数将多个DataFrame对象合并为一个新的DataFrame,根据共同的列进行连接。通过合并数据,可以将不同来源的数据整合在一起,以便进行综合分析。

  2. 数据透视表:使用pivot_table()函数创建数据透视表,基于不同的维度对数据进行汇总和聚合。

  3. 数据分组和聚合:使用groupby()函数对DataFrame对象进行分组,并应用聚合函数(如平均值、计数等)对每个组进行计算。通过分组和聚合,可以对数据进行更详细的分析和统计。

  4. 数据排序和筛选:使用sort_values()函数对DataFrame对象进行排序,根据特定的列进行升序或降序排序。同时,使用条件语句进行筛选,选择符合特定条件的行或列数据。

  5. 数据操作和转换:使用字符串处理函数(如split()explode()等)对字符串类型的列进行操作和转换,以提取有用的信息或将数据拆分成多个行。这些操作可以使数据更加便于分析和理解。

  6. 数据统计和计算:使用聚合函数(如mean()std()等)对数据进行统计和计算,揭示数据的统计特征和变化情况。


import pandas as pd
#文件皆位于根目录下
# 读取"users.dat"文件,它将列名设置为unames,并将分隔符设置为::。header=None参数表示文件中没有标题行,因此应使用unames中提供的列名。
unames = ["user_id", "gender", "age", "occupation", "zip"]
users = pd.read_table("datasets/movielens/users.dat", sep="::", header=None, names=unames, engine="python")

# 读取"ratings.dat"文件
rnames = ["user_id", "movie_id", "rating", "timestamp"]
ratings = pd.read_table("datasets/movielens/ratings.dat", sep="::", header=None, names=rnames, engine="python")

# 读取"movies.dat"文件
mnames = ["movie_id", "title", "genres"]
movies = pd.read_table("datasets/movielens/movies.dat", sep="::", header=None, names=mnames, engine="python")
#这段代码片段读取了三个文件("users.dat"、"ratings.dat"和"movies.dat"),
#并将它们分别存储在不同的pandas DataFrame中(users、ratings和movies),以便进一步分析或处理。
users.head(5)#输出用户DataFrame的前5行数据
ratings.head(5)# 输出评分DataFrame的前5行数据
movies.head(5)# 输出电影DataFrame的前5行数据
ratings# 输出完整的ratings DataFrame
user_id movie_id rating timestamp
0 1 1193 5 978300760
1 1 661 3 978302109
2 1 914 3 978301968
3 1 3408 4 978300275
4 1 2355 5 978824291
1000204 6040 1091 1 956716541
1000205 6040 1094 5 956704887
1000206 6040 562 5 956704746
1000207 6040 1096 4 956715648
1000208 6040 1097 4 956715569
#使用pd.merge()函数将ratings、users和movies三个DataFrame合并为一个名为data的新DataFrame。
#这个新的data DataFrame将包含了三个原始DataFrame中的所有列和行,基于它们之间的共同列进行合并。
data = pd.merge(pd.merge(ratings, users), movies)
data#输出data DataFrame合并后的所有数据
data.iloc[0]#输出data DataFrame中的第一行数据
user_id                                            1
movie_id                                        1193
rating                                             5
timestamp                                  978300760
gender                                             F
age                                                1
occupation                                        10
zip                                            48067
title         One Flew Over the Cuckoo's Nest (1975)
genres                                         Drama
Name: 0, dtype: object
#使用pivot_table()函数基于data DataFrame创建了一个名为mean_ratings的新DataFrame,
#该DataFrame计算了每个电影标题(title)在不同性别(gender)下的平均评分。
mean_ratings = data.pivot_table("rating", index="title",columns="gender", aggfunc="mean")
mean_ratings.head(5)#mean_ratings DataFrame 的前5行输出
gender F M
title
$1,000,000 Duck (1971) 3.375000 2.761905
'Night Mother (1986) 3.388889 3.352941
'Til There Was You (1997) 2.675676 2.733333
'burbs, The (1989) 2.793478 2.962085
…And Justice for All (1979) 3.828571 3.689024

Q:什么是数据透视表?

A:数据透视表是一种数据汇总和聚合的方法,通过对数据进行重新排列和汇总,可以提供对数据的多个维度的聚合结果。数据透视表可以帮助我们更好地理解数据之间的关系和趋势,从而支持更深入的数据分析和决策。数据透视表的主要优势是可以快速而灵活地汇总数据,并以一种直观的方式展示汇总结果。以下是数据透视表的几个关键要素:

1. 行索引:选择一个或多个列作为行索引,根据这些列的值进行数据的分组和分类。行索引决定了透视表中的每一行。

2. 列索引:选择一个或多个列作为列索引,根据这些列的值进行数据的分组和分类。列索引决定了透视表中的每一列。

3. 聚合值:选择一个或多个列作为聚合值,对这些列的值进行聚合计算,如求和、平均值、计数等。聚合值决定了透视表中每个单元格的内容。

4. 聚合函数:选择适当的聚合函数对聚合值进行计算。常见的聚合函数包括求和、平均值、计数、最大值、最小值等。

通过这些要素的组合,可以根据不同的需求和分析目标创建不同形式的数据透视表。数据透视表的结果可以提供对数据的多维度聚合结果,帮助我们发现数据中的模式、趋势和关联性。

#使用groupby()函数对data DataFrame按电影标题(title)进行分组,并计算每个电影标题对应的评分数量。
ratings_by_title = data.groupby("title").size()
ratings_by_title.head()#显示每个电影标题对应的评分数量。
active_titles = ratings_by_title.index[ratings_by_title >= 250]#从评分数量中筛选出了评分数大于等于250的活跃电影标题
active_titles
Index([''burbs, The (1989)', '10 Things I Hate About You (1999)',
       '101 Dalmatians (1961)', '101 Dalmatians (1996)', '12 Angry Men (1957)',
       '13th Warrior, The (1999)', '2 Days in the Valley (1996)',
       '20,000 Leagues Under the Sea (1954)', '2001: A Space Odyssey (1968)',
       '2010 (1984)',
       ...
       'X-Men (2000)', 'Year of Living Dangerously (1982)',
       'Yellow Submarine (1968)', 'You've Got Mail (1998)',
       'Young Frankenstein (1974)', 'Young Guns (1988)',
       'Young Guns II (1990)', 'Young Sherlock Holmes (1985)',
       'Zero Effect (1998)', 'eXistenZ (1999)'],
      dtype='object', name='title', length=1216)
#使用loc索引器从mean_ratings DataFrame 中选择评分数大于等于250的活跃电影标题
mean_ratings = mean_ratings.loc[active_titles]
mean_ratings
gender F M
title
'burbs, The (1989) 2.793478 2.962085
10 Things I Hate About You (1999) 3.646552 3.311966
101 Dalmatians (1961) 3.791444 3.500000
101 Dalmatians (1996) 3.240000 2.911215
12 Angry Men (1957) 4.184397 4.328421
Young Guns (1988) 3.371795 3.425620
Young Guns II (1990) 2.934783 2.904025
Young Sherlock Holmes (1985) 3.514706 3.363344
Zero Effect (1998) 3.864407 3.723140
eXistenZ (1999) 3.098592 3.289086
#使用rename()函数将mean_ratings DataFrame 中的索引标签从"Seven Samurai (The Magnificent Seven) (Shichinin no samurai) (1954)"更改为
#"Seven Samurai (Shichinin no samurai) (1954)"。
mean_ratings = mean_ratings.rename(index={"Seven Samurai (The Magnificent Seven) (Shichinin no samurai) (1954)":
                           "Seven Samurai (Shichinin no samurai) (1954)"})
#使用sort_values()函数根据女性观众的平均评分("F"列)对mean_ratings DataFrame 进行降序排序
top_female_ratings = mean_ratings.sort_values("F", ascending=False)
top_female_ratings.head()
gender F M diff
title
Close Shave, A (1995) 4.644444 4.473795 -0.170650
Wrong Trousers, The (1993) 4.588235 4.478261 -0.109974
Sunset Blvd. (a.k.a. Sunset Boulevard) (1950) 4.572650 4.464589 -0.108060
Wallace & Gromit: The Best of Aardman Animation (1996) 4.563107 4.385075 -0.178032
Schindler's List (1993) 4.562602 4.491415 -0.071187
#通过计算mean_ratings DataFrame 中男性观众平均评分("M"列)与女性观众平均评分("F"列)的差异,将差异值添加为名为"diff"的新列。
mean_ratings["diff"] = mean_ratings["M"] - mean_ratings["F"]
mean_ratings
gender F M diff
title
'burbs, The (1989) 2.793478 2.962085 0.168607
10 Things I Hate About You (1999) 3.646552 3.311966 -0.334586
101 Dalmatians (1961) 3.791444 3.500000 -0.291444
101 Dalmatians (1996) 3.240000 2.911215 -0.328785
12 Angry Men (1957) 4.184397 4.328421 0.144024
Young Guns (1988) 3.371795 3.425620 0.053825
Young Guns II (1990) 2.934783 2.904025 -0.030758
Young Sherlock Holmes (1985) 3.514706 3.363344 -0.151362
Zero Effect (1998) 3.864407 3.723140 -0.141266
eXistenZ (1999) 3.098592 3.289086 0.190494
#使用sort_values()函数根据差异值("diff"列)对mean_ratings DataFrame 进行升序排序
sorted_by_diff = mean_ratings.sort_values("diff")
sorted_by_diff.head()
gender F M diff
title
Dirty Dancing (1987) 3.790378 2.959596 -0.830782
Jumpin' Jack Flash (1986) 3.254717 2.578358 -0.676359
Grease (1978) 3.975265 3.367041 -0.608224
Little Women (1994) 3.870588 3.321739 -0.548849
Steel Magnolias (1989) 3.901734 3.365957 -0.535777
#通过使用切片操作[::-1]对sorted_by_diff DataFrame 进行逆序排序,并返回前5行内容。
sorted_by_diff[::-1].head(5)
gender F M diff
title
Good, The Bad and The Ugly, The (1966) 3.494949 4.221300 0.726351
Kentucky Fried Movie, The (1977) 2.878788 3.555147 0.676359
Dumb & Dumber (1994) 2.697987 3.336595 0.638608
Longest Day, The (1962) 3.411765 4.031447 0.619682
Cable Guy, The (1996) 2.250000 2.863787 0.613787
#使用groupby()函数按电影标题(title)分组,并计算每个电影标题对应的评分标准差。
rating_std_by_title = data.groupby("title")["rating"].std()
#从标准差结果中筛选出评分数大于等于250的活跃电影标题,
rating_std_by_title = rating_std_by_title.loc[active_titles]
rating_std_by_title.head()
title
'burbs, The (1989)                   1.107760
10 Things I Hate About You (1999)    0.989815
101 Dalmatians (1961)                0.982103
101 Dalmatians (1996)                1.098717
12 Angry Men (1957)                  0.812731
Name: rating, dtype: float64
#使用sort_values()函数将rating_std_by_title Series 对象按评分标准差进行降序排序,并返回前10个最大的标准差值。
rating_std_by_title.sort_values(ascending=False)[:10]
title
Dumb & Dumber (1994)                     1.321333
Blair Witch Project, The (1999)          1.316368
Natural Born Killers (1994)              1.307198
Tank Girl (1995)                         1.277695
Rocky Horror Picture Show, The (1975)    1.260177
Eyes Wide Shut (1999)                    1.259624
Evita (1996)                             1.253631
Billy Madison (1995)                     1.249970
Fear and Loathing in Las Vegas (1998)    1.246408
Bicentennial Man (1999)                  1.245533
Name: rating, dtype: float64
#展示"genres"列的前5行数据
movies["genres"].head()
#将每个电影的"genres"列按竖线字符分割成一个列表
movies["genres"].head().str.split("|")
#将分割后的列表赋值给了一个名为"genre"的新列,并删除了原始的"genres"列。这样,"genre"列包含了每个电影的类型列表。
movies["genre"] = movies.pop("genres").str.split("|")
movies.head()
movie_id title genre
0 1 Toy Story (1995) [Animation, Children's, Comedy]
1 2 Jumanji (1995) [Adventure, Children's, Fantasy]
2 3 Grumpier Old Men (1995) [Comedy, Romance]
3 4 Waiting to Exhale (1995) [Comedy, Drama]
4 5 Father of the Bride Part II (1995) [Comedy]
#使用explode()函数将"genre"列中的列表元素展开为多行,并将结果存储在名为"movies_exploded"的新DataFrame中。然后显示前10行的数据。
movies_exploded = movies.explode("genre")
movies_exploded[:10]
movie_id title genre
0 1 Toy Story (1995) Animation
0 1 Toy Story (1995) Children's
0 1 Toy Story (1995) Comedy
1 2 Jumanji (1995) Adventure
1 2 Jumanji (1995) Children's
1 2 Jumanji (1995) Fantasy
2 3 Grumpier Old Men (1995) Comedy
2 3 Grumpier Old Men (1995) Romance
3 4 Waiting to Exhale (1995) Comedy
3 4 Waiting to Exhale (1995) Drama
#使用pd.merge()函数将"movies_exploded"、"ratings"和"users"三个DataFrame进行合并,创建一个名为"ratings_with_genre"的新DataFrame。
ratings_with_genre = pd.merge(pd.merge(movies_exploded, ratings), users)
#选择"ratings_with_genre" DataFrame 中的第一行数据。
ratings_with_genre.iloc[0]
#使用groupby()函数对"ratings_with_genre" DataFrame 按"genre"和"age"分组,并计算每个组中"rating"列的平均值。
genre_ratings = (ratings_with_genre.groupby(["genre", "age"])
                 ["rating"].mean()#使用mean()函数计算每个分组的平均值
                 .unstack("age"))#使用unstack("age")将结果重塑为以"age"作为列索引的形式。
genre_ratings[:10]
age 1 18 25 35 45 50 56
genre
Action 3.506385 3.447097 3.453358 3.538107 3.528543 3.611333 3.610709
Adventure 3.449975 3.408525 3.443163 3.515291 3.528963 3.628163 3.649064
Animation 3.476113 3.624014 3.701228 3.740545 3.734856 3.780020 3.756233
Children's 3.241642 3.294257 3.426873 3.518423 3.527593 3.556555 3.621822
Comedy 3.497491 3.460417 3.490385 3.561984 3.591789 3.646868 3.650949
Crime 3.710170 3.668054 3.680321 3.733736 3.750661 3.810688 3.832549
Documentary 3.730769 3.865865 3.946690 3.953747 3.966521 3.908108 3.961538
Drama 3.794735 3.721930 3.726428 3.782512 3.784356 3.878415 3.933465
Fantasy 3.317647 3.353778 3.452484 3.482301 3.532468 3.581570 3.532700
Film-Noir 4.145455 3.997368 4.058725 4.064910 4.105376 4.175401 4.125932

2.美国1880-2010年的婴儿名字

美国社会保障局(SSA)提供了从1880年至现在的婴儿姓名频率的数据。可以使用这些数据做很多事情:
根据给定的名字对婴儿名字随时间的比例进行可视化
确定一个名字的相对排位
确定每年最受欢迎的名字,或者流行程度最高或最低的名字


接下来我们将结合代码共同学习这一分析过程。本例题涉及以下知识点,重要处会有解释:

  1. 数据分组和聚合:通过使用groupby()函数对DataFrame进行分组操作,可以根据指定的列对数据进行分组。在代码中,使用groupby()函数按年份和性别进行分组,并计算每个分组的出生人数总和。

  2. 数据排序和排名:通过使用sort_values()函数对DataFrame进行排序操作,可以根据指定的列对数据进行排序。在代码中,使用sort_values()函数对数据进行降序排序,以获取出生人数最多的姓名。

  3. 数据透视:通过使用pivot_table()函数,可以对数据进行透视操作,计算每个分组的汇总统计信息。在代码中,使用透视表计算了按年份和性别分组的出生数总和,并将结果存储在total_birthstable这两个DataFrame中。

  4. 数据可视化:使用Matplotlib库的plot()函数对数据进行可视化。代码中使用折线图和柱状图展示了不同年份和性别的出生人数、比例等信息。通过数据可视化可以更直观地观察数据的趋势和分布情况。

  5. 数据处理和筛选:通过对DataFrame应用函数、使用条件语句和基于特定列的筛选,可以对数据进行处理和筛选。在代码中,使用apply()函数、sort_values()函数、isin()函数等对数据进行处理和筛选,以满足特定的需求。


#在读取时,为列指定了列名为["name", "sex", "births"]。
names1880 = pd.read_csv("datasets/babynames/yob1880.txt",
                        names=["name", "sex", "births"])
names1880
name sex births
0 Mary F 7065
1 Anna F 2604
2 Emma F 2003
3 Elizabeth F 1939
4 Minnie F 1746
1995 Woodie M 5
1996 Worthy M 5
1997 Wright M 5
1998 York M 5
1999 Zachariah M 5
#对 names1880 DataFrame 进行分组,按照性别("sex")进行分组,并计算每个性别对应的出生数("births" 列)的总和。
names1880.groupby("sex")["births"].sum()
sex
F     90993
M    110493
Name: births, dtype: int64

Q:如何对数据进行分组和聚合?

A:在数据分析中,经常需要根据某个特定的列对数据进行分组,并对每个分组进行聚合操作,以计算统计指标或得出结论。在本段代码中,我们通过使用groupby()函数按照年份和性别对数据进行分组,然后使用sum()函数计算每个分组的出生人数总和。这样可以帮助我们理解和描述数据的整体趋势,比如年份和性别对出生人数的影响。

pieces = []
#代码通过循环遍历从1880年到2010年的文件,并将每个文件的内容读取为DataFrame
for year in range(1880, 2011):
    path = f"datasets/babynames/yob{year}.txt"
    frame = pd.read_csv(path, names=["name", "sex", "births"])

    #为每个DataFrame添加一个名为"year"的列
    frame["year"] = year
    pieces.append(frame)

#用pd.concat()函数将所有DataFrame连接成一个单独的DataFrame
names = pd.concat(pieces, ignore_index=True)
names#显示该Frame
name sex births year
0 Mary F 7065 1880
1 Anna F 2604 1880
2 Emma F 2003 1880
3 Elizabeth F 1939 1880
4 Minnie F 1746 1880
1690779 Zymaire M 5 2010
1690780 Zyonne M 5 2010
1690781 Zyquarius M 5 2010
1690782 Zyran M 5 2010
1690783 Zzyzx M 5 2010
#使用pivot_table()函数计算了按年份和性别分组的出生数总和
total_births = names.pivot_table("births", index="year",
                                 columns="sex", aggfunc=sum)
#显示total_births DataFrame 的最后几行
total_births.tail()
#绘制了一个标题为"Total births by sex and year"的出生数按年份和性别的折线图。
total_births.plot(title="Total births by sex and year")

 

#定义了一个名为add_prop的函数。该函数接收一个分组,并为该分组计算一个名为"prop"的新列,表示每个名字在该年份和性别分组中的比例,每个名字的出生率
def add_prop(group):
    group["prop"] = group["births"] / group["births"].sum()
    return group
#将names DataFrame 按年份和性别分组,并对每个分组应用add_prop函数
names = names.groupby(["year", "sex"], group_keys=False).apply(add_prop)
name sex births year prop
0 Mary F 7065 1880 0.077643
1 Anna F 2604 1880 0.028618
2 Emma F 2003 1880 0.022013
3 Elizabeth F 1939 1880 0.021309
4 Minnie F 1746 1880 0.019188
1690779 Zymaire M 5 2010 0.000003
1690780 Zyonne M 5 2010 0.000003
1690781 Zyquarius M 5 2010 0.000003
1690782 Zyran M 5 2010 0.000003
1690783 Zzyzx M 5 2010 0.000003
#按照 "year" 和 "sex" 进行分组,并计算每个组中的 "prop" 列的总和。
names.groupby(["year", "sex"])["prop"].sum()
year  sex
1880  F      1.0
      M      1.0
1881  F      1.0
      M      1.0
1882  F      1.0
            ... 
2008  M      1.0
2009  F      1.0
      M      1.0
2010  F      1.0
      M      1.0
Name: prop, Length: 262, dtype: float64
#该函数接收一个分组,并按照 "births" 列(出生人数)的降序对分组进行排序,然后选择排序结果中的前1000行
def get_top1000(group):
    return group.sort_values("births", ascending=False)[:1000]
#按照 "year" 和 "sex" 进行分组,并对每个分组应用 get_top1000 函数
grouped = names.groupby(["year", "sex"])
top1000 = grouped.apply(get_top1000)
top1000.head()
name sex births year prop
year sex
1880 F 0 Mary F 7065 1880 0.077643
1 Anna F 2604 1880 0.028618
2 Emma F 2003 1880 0.022013
3 Elizabeth F 1939 1880 0.021309
4 Minnie F 1746 1880 0.019188
#使用reset_index()函数对top1000 DataFrame 进行重置索引,并丢弃原始索引。设置drop=True可以移除原始索引列,以便在重置索引后不保留它。
top1000 = top1000.reset_index(drop=True)
top1000.head()#显示出生人数最多的前5名
name sex births year prop
0 Mary F 7065 1880 0.077643
1 Anna F 2604 1880 0.028618
2 Emma F 2003 1880 0.022013
3 Elizabeth F 1939 1880 0.021309
4 Minnie F 1746 1880 0.019188
#代码根据 "sex" 列的值将 top1000 DataFrame 分为男孩("M")和女孩("F")两个子数据集。
boys = top1000[top1000["sex"] == "M"]
girls = top1000[top1000["sex"] == "F"]
#使用 pivot_table() 函数对 top1000 DataFrame 进行数据透视,计算了每个名字在不同年份下的出生数总和
total_births = top1000.pivot_table("births", index="year",
                                   columns="name",
                                   aggfunc=sum)
#码使用 info() 方法打印出 total_births DataFrame 的信息,然后从中选择了 "John"、"Harry"、"Mary" 和 "Marilyn" 四个名字的列,
#并绘制了一个子图展示每年的出生数。
total_births.info()
#选择这四个名字进行绘制
subset = total_births[["John", "Harry", "Mary", "Marilyn"]]
#使用 plot() 方法对 subset DataFrame 进行绘图,设置 subplots=True 以绘制子图,figsize=(12, 10) 用于设置图形的大小
subset.plot(subplots=True, figsize=(12, 10),
            title="Number of births per year")

import matplotlib.pyplot as plt#创建一个新的画布。
plt.figure()
#使用 pivot_table() 函数计算了 top1000 DataFrame 中 的出生率总和
table = top1000.pivot_table("prop", index="year",
                            columns="sex", aggfunc=sum)
#使用 plot() 方法绘制了一个折线图,展示了每年男女出生率关系
table.plot(title="Sum of table1000.prop by year and sex",
           yticks=np.linspace(0, 1.2, 13))

 

df = boys[boys["year"] == 2010]#2010年男孩出生人数表
df
name sex births year prop
260877 Jacob M 21875 2010 0.011523
260878 Ethan M 17866 2010 0.009411
260879 Michael M 17133 2010 0.009025
260880 Jayden M 17030 2010 0.008971
260881 William M 16870 2010 0.008887
261872 Camilo M 194 2010 0.000102
261873 Destin M 194 2010 0.000102
261874 Jaquan M 194 2010 0.000102
261875 Jaydan M 194 2010 0.000102
261876 Maxton M 193 2010 0.000102
#对 DataFrame 中的 "prop" 列进行排序,并计算累计和。然后,展示了累计和的前10个值,
#并使用 searchsorted() 方法找到累计和达到 0.5 时的索引位置。
prop_cumsum = df["prop"].sort_values(ascending=False).cumsum()
prop_cumsum[:10]
prop_cumsum.searchsorted(0.5)
116
#从名为 boys 的 DataFrame 中选择了年份为 1900 的数据,并对该子数据集按 "prop" 列进行降序排序。
#然后,计算累计和,并使用 searchsorted() 方法找到累计和达到 0.5 时的索引位置,最后加上 1。
df = boys[boys.year == 1900]
in1900 = df.sort_values("prop", ascending=False).prop.cumsum()
in1900.searchsorted(0.5) + 1
25
#定义了一个名为 get_quantile_count 的函数。该函数接收一个分组,并按照 "prop" 列的值进行降序排序。然后,计算累计和,
#并使用 searchsorted() 方法找到累计和达到给定分位数 q 时的位置,最后加上 1。
def get_quantile_count(group, q=0.5):
    group = group.sort_values("prop", ascending=False)
    return group.prop.cumsum().searchsorted(q) + 1

diversity = top1000.groupby(["year", "sex"]).apply(get_quantile_count)
diversity = diversity.unstack()
fig = plt.figure()
diversity.head()
#使用 plot() 方法绘制了一个图表,标题为 "Number of popular names in top 50%"。图表展示了每个年份和性别组合中达到前50%的名字数量。
diversity.plot(title="Number of popular names in top 50%")

#定义了一个名为 get_last_letter 的函数。该函数接收一个字符串 x,并返回字符串的最后一个字母。
def get_last_letter(x):
    return x[-1]
#使用 map() 方法将 get_last_letter 函数应用于 names DataFrame 的 "name" 列,将每个名字的最后一个字母提取出来
last_letters = names["name"].map(get_last_letter)
last_letters.name = "last_letter"
#使用 pivot_table() 函数对 names DataFrame 进行数据透视,计算每个年份和性别组合中,根据最后一个字母进行分组的出生数总和
table = names.pivot_table("births", index=last_letters,
                          columns=["sex", "year"], aggfunc=sum)
#将展示按照特定年份(1910、1960 和 2010)的出生数数据,并且按照性别和最后一个字母的分布。
subtable = table.reindex(columns=[1910, 1960, 2010], level="year")
subtable.head()
sex F M
year 1910 1960 2010 1910 1960 2010
last_letter
a 108376.0 691247.0 670605.0 977.0 5204.0 28438.0
b NaN 694.0 450.0 411.0 3912.0 38859.0
c 5.0 49.0 946.0 482.0 15476.0 23125.0
d 6750.0 3729.0 2607.0 22111.0 262112.0 44398.0
e 133569.0 435013.0 313833.0 28655.0 178823.0 129012.0
#展示每个年份和性别组合中每个字母的比例,以及它们在对应年份的出生数总和的占比。
subtable.sum()
letter_prop = subtable / subtable.sum()
letter_prop
sex F M
year 1910 1960 2010 1910 1960 2010
last_letter
a 0.273390 0.341853 0.381240 0.005031 0.002440 0.014980
b NaN 0.000343 0.000256 0.002116 0.001834 0.020470
c 0.000013 0.000024 0.000538 0.002482 0.007257 0.012181
d 0.017028 0.001844 0.001482 0.113858 0.122908 0.023387
e 0.336941 0.215133 0.178415 0.147556 0.083853 0.067959
f NaN 0.000010 0.000055 0.000783 0.004325 0.001188
g 0.000144 0.000157 0.000374 0.002250 0.009488 0.001404
h 0.051529 0.036224 0.075852 0.045562 0.037907 0.051670
i 0.001526 0.039965 0.031734 0.000844 0.000603 0.022628
j NaN NaN 0.000090 NaN NaN 0.000769
k 0.000121 0.000156 0.000356 0.036581 0.049384 0.018541
l 0.043189 0.033867 0.026356 0.065016 0.104904 0.070367
m 0.001201 0.008613 0.002588 0.058044 0.033827 0.024657
n 0.079240 0.130687 0.140210 0.143415 0.152522 0.362771
o 0.001660 0.002439 0.001243 0.017065 0.012829 0.042681
p 0.000018 0.000023 0.000020 0.003172 0.005675 0.001269
q NaN NaN 0.000030 NaN NaN 0.000180
r 0.013390 0.006764 0.018025 0.064481 0.031034 0.087477
s 0.039042 0.012764 0.013332 0.130815 0.102730 0.065145
t 0.027438 0.015201 0.007830 0.072879 0.065655 0.022861
u 0.000684 0.000574 0.000417 0.000124 0.000057 0.001221
v NaN 0.000060 0.000117 0.000113 0.000037 0.001434
w 0.000020 0.000031 0.001182 0.006329 0.007711 0.016148
x 0.000015 0.000037 0.000727 0.003965 0.001851 0.008614
y 0.110972 0.152569 0.116828 0.077349 0.160987 0.058168
z 0.002439 0.000659 0.000704 0.000170 0.000184 0.001831
import matplotlib.pyplot as plt
#使用 Matplotlib 库绘制了一个包含两个子图的图形窗口,每个子图展示了不同性别的字母比例数据
fig, axes = plt.subplots(2, 1, figsize=(10, 8))
#在第一个子图中绘制了一个垂直柱状图。kind="bar" 表示绘制柱状图,rot=0 表示不旋转 x 轴标签,ax=axes[0] 表示绘制在第一个子图上,title="Male" 设置了子图的标题为 "Male",即男性。
letter_prop["M"].plot(kind="bar", rot=0, ax=axes[0], title="Male")
#在第二个子图中绘制了一个垂直柱状图。kind="bar" 表示绘制柱状图,rot=0 表示不旋转 x 轴标签,ax=axes[1] 表示绘制在第二个子图上,
#title="Female" 设置了子图的标题为 "Female",即女性。legend=False 表示不显示图例。
letter_prop["F"].plot(kind="bar", rot=0, ax=axes[1], title="Female",
                      legend=False)

plt.subplots_adjust(hspace=0.25)  # 调整子图之间的垂直间距

letter_prop = table / table.sum()  # 计算每个字母在每年出生人数中的比例

dny_ts = letter_prop.loc[["d", "n", "y"], "M"].T  # 从letter_prop中选择字母"d", "n", "y"在男性出生人数中的比例,并进行转置操作

dny_ts.head()  # 显示dny_ts DataFrame的前几行数据
last_letter d n y
year
1880 0.083055 0.153213 0.075760
1881 0.083247 0.153214 0.077451
1882 0.085340 0.149560 0.077537
1883 0.084066 0.151646 0.079144
1884 0.086120 0.149915 0.080405
plt.close("all")  # 关闭所有打开的图形窗口
fig = plt.figure()  # 创建一个新的Figure对象
dny_ts.plot()  # 对dny_ts DataFrame进行折线图的可视化

all_names = pd.Series(top1000["name"].unique())  # 从top1000 DataFrame的"name"列获取唯一的姓名,并将结果存储在all_names变量中

lesley_like = all_names[all_names.str.contains("Lesl")]  # 选择all_names中包含"Lesl"的姓名,并将结果赋值给lesley_like变量

lesley_like  # 显示lesley_like Series,即包含以"Lesl"开头的姓名
632     Leslie
2294    Lesley
4262    Leslee
4728     Lesli
6103     Lesly
dtype: object
filtered = top1000[top1000["name"].isin(lesley_like)]  # 根据top1000 DataFrame中的"name"列与lesley_like中的姓名进行匹配,筛选出匹配的行数据,并将结果赋值给filtered变量

filtered.groupby("name")["births"].sum()  # 对filtered DataFrame按姓名进行分组,计算每个姓名的出生人数总和,并显示结果
name
Leslee      1082
Lesley     35022
Lesli        929
Leslie    370429
Lesly      10067
Name: births, dtype: int64
table = filtered.pivot_table("births", index="year", columns="sex", aggfunc="sum")  # 根据年份和性别对filtered DataFrame进行透视,计算每个年份和性别的出生人数总和,并将结果存储在table变量中

table = table.div(table.sum(axis="columns"), axis="index")  # 对table DataFrame进行行归一化,即每行的总和作为除数,计算每个年份和性别的归一化比例

table.tail()  # 显示table DataFrame的最后几行数据,即归一化后的比例结果
sex F M
year
2006 1.0 NaN
2007 1.0 NaN
2008 1.0 NaN
2009 1.0 NaN
2010 1.0 NaN
fig = plt.figure()  # 创建一个新的Figure对象

table.plot(style={"M": "k-", "F": "k--"})  # 对table DataFrame进行折线图的可视化,男性使用黑色实线,女性使用黑色虚线


3.美国农业部视频数据库

美国农业部提供了食物营养信息数据库。每种事务都有一些识别属性以及两份营养元素和营养比例的列表。这种形式的数据不适合分析,所以需要做一些工作将数据转换成更好的形式。


接下来我们将结合代码共同学习这一分析过程。本例题涉及以下知识点,重要处会有代码注释:

  1. 创建和操作Pandas DataFrame:使用pd.DataFrame()函数创建Pandas DataFrame对象,使用head()方法查看DataFrame的前几行数据,使用info()方法查看DataFrame的基本信息。

  2. 数据筛选和计数:使用pd.value_counts()函数对选定列中的每个唯一值进行计数。

  3. 循环和列表操作:使用for循环遍历列表中的每个元素,使用append()方法将元素添加到列表中。

  4. 合并DataFrame:使用pd.concat()函数将多个DataFrame对象合并为一个大的DataFrame,使用merge()函数基于指定的列将两个DataFrame合并。

  5. 数据清洗:使用drop_duplicates()方法删除重复的行。

  6. 列重命名:使用rename()方法重命名DataFrame的列名。

  7. 可视化:使用matplotlib.pyplot库进行可视化,使用plot()方法绘制条形图。

  8. 分组和聚合:使用groupby()方法对DataFrame进行分组,并使用聚合函数计算每个分组的统计量。


import json
db = json.load(open("datasets/usda_food/database.json"))

# 计算对象db的长度,即列表中元素的数量
len(db)
# 获取db列表中索引为0的元素的所有键
db[0].keys()

# 从db列表中索引为0的元素中获取键为"nutrients"的值的列表,并返回列表中的第一个元素
db[0]["nutrients"][0]

# *注意*将db列表中索引为0的元素中的"nutrients"值转换为Pandas DataFrame对象
nutrients = pd.DataFrame(db[0]["nutrients"])

# 显示nutrients DataFrame的前7行数据
nutrients.head(7)
value units description group
0 25.18 g Protein Composition
1 29.20 g Total lipid (fat) Composition
2 3.06 g Carbohydrate, by difference Composition
3 3.28 g Ash Other
4 376.00 kcal Energy Energy
5 39.28 g Water Composition
6 1573.00 kJ Energy Energy

Q:为什么需要将db列表中索引为0的元素中的"nutrients"值转换为Pandas DataFrame对象?

A:创建和操作Pandas DataFrame:nutrients = pd.DataFrame(db[0]["nutrients"])将db列表中索引为0的元素中的"nutrients"值转换为Pandas DataFrame对象,并将其存储在nutrients变量中。这样我们可以使用Pandas提供的函数和方法对数据进行处理和分析。nutrients.head(7)使用head()方法显示nutrients DataFrame的前7行数据。

info_keys = ["description", "group", "id", "manufacturer"]  # 包含要从数据库中提取的信息的键的列表
info = pd.DataFrame(db, columns=info_keys)  # 使用info_keys作为列名,创建包含db数据的Pandas DataFrame对象,并将其存储在info变量中
info.head()  # 显示info DataFrame的前几行数据
info.info()  # 显示info DataFrame的基本信息,包括列名、每列的非空值数量、每列的数据类型等
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6636 entries, 0 to 6635
Data columns (total 4 columns):
 #   Column        Non-Null Count  Dtype 
---  ------        --------------  ----- 
 0   description   6636 non-null   object
 1   group         6636 non-null   object
 2   id            6636 non-null   int64 
 3   manufacturer  5195 non-null   object
dtypes: int64(1), object(3)
memory usage: 207.5+ KB
pd.value_counts(info["group"])[:10]
'''
info["group"]:从DataFrame info 中选择了名为 "group" 的列,该列包含了食物的分组信息。
pd.value_counts():对选定列中的每个唯一值进行计数,并返回计数结果。
[:10]:取计数结果中的前 10 个值,即返回出现次数最多的前 10 个分组
'''
Vegetables and Vegetable Products    812
Beef Products                        618
Baked Products                       496
Breakfast Cereals                    403
Legumes and Legume Products          365
Fast Foods                           365
Lamb, Veal, and Game Products        345
Sweets                               341
Fruits and Fruit Juices              328
Pork Products                        328
Name: group, dtype: int64

Q:如何进行数据筛选和计数?

A:info_keys = ["description", "group", "id", "manufacturer"]定义了一个包含要从数据库中提取的信息的键的列表。

info = pd.DataFrame(db, columns=info_keys)使用info_keys作为列名,创建包含db数据的Pandas DataFrame对象,并将其存储在info变量中。

info.head()显示info DataFrame的前几行数据。info.info()显示info DataFrame的基本信息,包括列名、每列的非空值数量、每列的数据类型等。

pd.value_counts(info["group"])[:10]对info["group"]列中的每个唯一值进行计数,并返回计数结果的前10个值,即返回出现次数最多的前10个分组。

nutrients = []  # 创建一个空列表用于存储营养信息

for rec in db:
    fnuts = pd.DataFrame(rec["nutrients"])  # 为每个记录创建一个包含营养信息的DataFrame对象
    fnuts["id"] = rec["id"]  # 添加一个名为"id"的列,将记录的id值赋给该列的每个元素
    nutrients.append(fnuts)  # 将每个记录的营养信息DataFrame添加到nutrients列表中

nutrients = pd.concat(nutrients, ignore_index=True)  # 将nutrients列表中的DataFrame对象合并为一个大的DataFrame,并重新索引行号

nutrients
value units description group id
0 25.180 g Protein Composition 1008
1 29.200 g Total lipid (fat) Composition 1008
2 3.060 g Carbohydrate, by difference Composition 1008
3 3.280 g Ash Other 1008
4 376.000 kcal Energy Energy 1008
389350 0.000 mcg Vitamin B-12, added Vitamins 43546
389351 0.000 mg Cholesterol Other 43546
389352 0.072 g Fatty acids, total saturated Other 43546
389353 0.028 g Fatty acids, total monounsaturated Other 43546
389354 0.041 g Fatty acids, total polyunsaturated Other 43546
nutrients.duplicated().sum()  # 统计重复行的数量

nutrients = nutrients.drop_duplicates()  # 删除重复的行

col_mapping = {"description" : "food",
               "group"       : "fgroup"}

info = info.rename(columns=col_mapping, copy=False)  # 重命名info DataFrame的列名为food和fgroup

info.info()  # 显示info DataFrame的基本信息

col_mapping = {"description" : "nutrient",
               "group" : "nutgroup"}

nutrients = nutrients.rename(columns=col_mapping, copy=False)  # 重命名nutrients DataFrame的列名为nutrient和nutgroup

nutrients  # 显示重命名后的nutrients DataFrame
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6636 entries, 0 to 6635
Data columns (total 4 columns):
 #   Column        Non-Null Count  Dtype 
---  ------        --------------  ----- 
 0   food          6636 non-null   object
 1   fgroup        6636 non-null   object
 2   id            6636 non-null   int64 
 3   manufacturer  5195 non-null   object
dtypes: int64(1), object(3)
memory usage: 207.5+ KB
value units nutrient nutgroup id
0 25.180 g Protein Composition 1008
1 29.200 g Total lipid (fat) Composition 1008
2 3.060 g Carbohydrate, by difference Composition 1008
3 3.280 g Ash Other 1008
4 376.000 kcal Energy Energy 1008
389350 0.000 mcg Vitamin B-12, added Vitamins 43546
389351 0.000 mg Cholesterol Other 43546
389352 0.072 g Fatty acids, total saturated Other 43546
389353 0.028 g Fatty acids, total monounsaturated Other 43546
389354 0.041 g Fatty acids, total polyunsaturated Other 43546
ndata = pd.merge(nutrients, info, on="id")  # 基于"id"列将nutrients和info两个DataFrame进行合并,并将结果存储在ndata中

ndata.info()  # 显示ndata DataFrame的基本信息

ndata.iloc[30000]  # 获取ndata DataFrame中索引为30000的行的数据

<class 'pandas.core.frame.DataFrame'>
Int64Index: 375176 entries, 0 to 375175
Data columns (total 8 columns):
 #   Column        Non-Null Count   Dtype  
---  ------        --------------   -----  
 0   value         375176 non-null  float64
 1   units         375176 non-null  object 
 2   nutrient      375176 non-null  object 
 3   nutgroup      375176 non-null  object 
 4   id            375176 non-null  int64  
 5   food          375176 non-null  object 
 6   fgroup        375176 non-null  object 
 7   manufacturer  293054 non-null  object 
dtypes: float64(1), int64(1), object(6)
memory usage: 25.8+ MB
value                                             0.04
units                                                g
nutrient                                       Glycine
nutgroup                                   Amino Acids
id                                                6158
food            Soup, tomato bisque, canned, condensed
fgroup                      Soups, Sauces, and Gravies
manufacturer                                          
Name: 30000, dtype: object
fig = plt.figure()  # 创建一个新的空白Figure对象,并将其赋值给变量fig

result = ndata.groupby(["nutrient", "fgroup"])["value"].quantile(0.5)  # 根据"nutrient"和"fgroup"列进行分组,计算"value"列的中位数,并将结果存储在result变量中
result["Zinc, Zn"].sort_values().plot(kind="barh")  # 从result中选择"Zinc, Zn"列的数据,对数据进行排序,然后使用水平条形图进行可视化

Q:如何进行可视化?

A:在Python中,有多个库可以用于数据可视化,下面是一些基本的可视化操作示例:

1.折线图:使用matplotlib库可以创建折线图来显示数据的趋势和变化:

import matplotlib.pyplot as plt

# 创建数据
x = [1, 2, 3, 4, 5]
y = [10, 15, 7, 12, 9]

# 绘制折线图
plt.plot(x, y)

# 添加标签和标题
plt.xlabel('X轴')
plt.ylabel('Y轴')
plt.title('折线图')

# 显示图形
plt.show()

2.柱状图:柱状图常用于显示不同类别之间的比较:

import matplotlib.pyplot as plt

# 创建数据
categories = ['A', 'B', 'C', 'D']
values = [10, 15, 7, 12]

# 绘制柱状图
plt.bar(categories, values)

# 添加标签和标题
plt.xlabel('类别')
plt.ylabel('数值')
plt.title('柱状图')

# 显示图形
plt.show()

3.散点图:散点图用于显示两个变量之间的关系:

import matplotlib.pyplot as plt

# 创建数据
x = [1, 2, 3, 4, 5]
y = [10, 15, 7, 12, 9]

# 绘制散点图
plt.scatter(x, y)

# 添加标签和标题
plt.xlabel('X轴')
plt.ylabel('Y轴')
plt.title('散点图')

# 显示图形
plt.show()

4.直方图:直方图用于显示数据的分布情况:

import matplotlib.pyplot as plt

# 创建数据
data = [1, 2, 2, 3, 3, 3, 4, 4, 5]

# 绘制直方图
plt.hist(data)

# 添加标签和标题
plt.xlabel('数值')
plt.ylabel('频数')
plt.title('直方图')

# 显示图形
plt.show()

回到本例题: 

by_nutrient = ndata.groupby(["nutgroup", "nutrient"])  # 根据"nutgroup"和"nutrient"列进行分组,将结果存储在by_nutrient变量中

def get_maximum(x):
    return x.loc[x.value.idxmax()]  # 定义一个函数get_maximum,用于获取每个分组中"value"列取得最大值的行

max_foods = by_nutrient.apply(get_maximum)[["value", "food"]]  # 对每个分组应用get_maximum函数,获取"value"列最大值所对应的行,并选择"value"和"food"两列

max_foods["food"] = max_foods["food"].str[:50]  # 将"food"列的字符串长度截取为最多50个字符

max_foods.loc["Amino Acids"]["food"]  # 从 max_foods DataFrame 中选择索引为 "Amino Acids" 的行,返回一个包含该行数据的 Series。从该 Series 中选择名为 "food" 的列,返回 "Amino Acids" 分类下食物名称的数据。
nutrient
Alanine                           Gelatins, dry powder, unsweetened
Arginine                               Seeds, sesame flour, low-fat
Aspartic acid                                   Soy protein isolate
Cystine                Seeds, cottonseed flour, low fat (glandless)
Glutamic acid                                   Soy protein isolate
Glycine                           Gelatins, dry powder, unsweetened
Histidine                Whale, beluga, meat, dried (Alaska Native)
Hydroxyproline    KENTUCKY FRIED CHICKEN, Fried Chicken, ORIGINA...
Isoleucine        Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Leucine           Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Lysine            Seal, bearded (Oogruk), meat, dried (Alaska Na...
Methionine                    Fish, cod, Atlantic, dried and salted
Phenylalanine     Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Proline                           Gelatins, dry powder, unsweetened
Serine            Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Threonine         Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Tryptophan         Sea lion, Steller, meat with fat (Alaska Native)
Tyrosine          Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Valine            Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Name: food, dtype: object

4.2012年联邦选举委员会数据库

美国联邦选举委员会公布了有关政治运动贡献的数据。这些数据包括捐赠者姓名、职业和雇主、地址和缴费金额。你可以尝试做以下分析:
1.按职业和雇主的捐赠统计;2.按捐赠金额统计;3.按州进行统计。


接下来我们将结合代码共同学习这一分析过程。本例题涉及以下知识点,重要处会有解释:

  1. 获取唯一值:fec["cand_nm"].unique()fec DataFrame的"cand_nm"列获取唯一的候选人姓名,并将结果存储在unique_cands变量中。

  2. 数据分组:grouped = fec.groupby("cand_nm")根据候选人姓名对fec DataFrame进行分组。

  3. 数据透视表:by_occupation = fec.pivot_table("contb_receipt_amt", index="contbr_occupation", columns="party", aggfunc="sum")使用pivot_table()函数创建数据透视表,对"contb_receipt_amt"列进行求和聚合,以"contbr_occupation"为行索引,以"party"为列索引。

  4. 可视化:over_2mm.plot(kind="barh")对DataFrame进行可视化,例如水平条形图。

  5. 数据映射:使用字典对列中的值进行映射,例如fec["contbr_occupation"].map(get_occ)使用自定义函数get_occ将"contbr_occupation"列中的值进行映射。

  6. 数据堆叠和展开:grouped.size().unstack(level=0)计算每个候选人在不同区间标签下的数据量,并将结果转换为宽格式,以候选人姓名为列。

  7. 数据透视表和缺失值填充:totals = grouped["contb_receipt_amt"].sum().unstack(level=0).fillna(0)计算每个候选人在不同州的总捐款金额,将结果转换为宽格式,并用0填充缺失值。

  8. 数据归一化和比例计算:percent = totals.div(totals.sum(axis="columns"), axis="index")对每个州的总捐款金额进行归一化,以每行的总和作为除数,计算归一化后的比例。


fec = pd.read_csv("datasets/fec/P00000001-ALL.csv", low_memory=False)  # 从CSV文件中读取数据,并将结果存储在fec变量中,low_memory参数设置为False以确保读取所有数据

fec.info()  # 显示fec DataFrame的基本信息

fec.iloc[123456]  # 获取fec DataFrame中索引为123456的行的数据
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1001731 entries, 0 to 1001730
Data columns (total 16 columns):
 #   Column             Non-Null Count    Dtype  
---  ------             --------------    -----  
 0   cmte_id            1001731 non-null  object 
 1   cand_id            1001731 non-null  object 
 2   cand_nm            1001731 non-null  object 
 3   contbr_nm          1001731 non-null  object 
 4   contbr_city        1001712 non-null  object 
 5   contbr_st          1001727 non-null  object 
 6   contbr_zip         1001620 non-null  object 
 7   contbr_employer    988002 non-null   object 
 8   contbr_occupation  993301 non-null   object 
 9   contb_receipt_amt  1001731 non-null  float64
 10  contb_receipt_dt   1001731 non-null  object 
 11  receipt_desc       14166 non-null    object 
 12  memo_cd            92482 non-null    object 
 13  memo_text          97770 non-null    object 
 14  form_tp            1001731 non-null  object 
 15  file_num           1001731 non-null  int64  
dtypes: float64(1), int64(1), object(14)
memory usage: 122.3+ MB
cmte_id                             C00431445
cand_id                             P80003338
cand_nm                         Obama, Barack
contbr_nm                         ELLMAN, IRA
contbr_city                             TEMPE
contbr_st                                  AZ
contbr_zip                          852816719
contbr_employer      ARIZONA STATE UNIVERSITY
contbr_occupation                   PROFESSOR
contb_receipt_amt                        50.0
contb_receipt_dt                    01-DEC-11
receipt_desc                              NaN
memo_cd                                   NaN
memo_text                                 NaN
form_tp                                 SA17A
file_num                               772372
Name: 123456, dtype: object
unique_cands = fec["cand_nm"].unique()  # 从fec DataFrame的"cand_nm"列获取唯一的候选人姓名,将结果存储在unique_cands变量中
unique_cands  # 显示unique_cands数组,即唯一的候选人姓名列表
unique_cands[2]  # 获取unique_cands数组中索引为2的元素,即第三个候选人的姓名
parties = {"Bachmann, Michelle": "Republican",
           "Cain, Herman": "Republican",
           "Gingrich, Newt": "Republican",
           "Huntsman, Jon": "Republican",
           "Johnson, Gary Earl": "Republican",
           "McCotter, Thaddeus G": "Republican",
           "Obama, Barack": "Democrat",
           "Paul, Ron": "Republican",
           "Pawlenty, Timothy": "Republican",
           "Perry, Rick": "Republican",
           "Roemer, Charles E. 'Buddy' III": "Republican",
           "Romney, Mitt": "Republican",
           "Santorum, Rick": "Republican"}
# 设置党派
fec["cand_nm"][123456:123461]  # 获取fec DataFrame中"cand_nm"列索引为123456到123460的行的数据
fec["cand_nm"][123456:123461].map(parties)  # 对索引为123456到123460的"cand_nm"列的数据应用parties字典,将候选人姓名映射为对应的党派
fec["party"] = fec["cand_nm"].map(parties)  # 将通过parties字典映射后的党派信息添加为fec DataFrame的一个新列,列名为"party"
fec["party"].value_counts()  # 统计"party"列中每个党派的数量,并返回计数结果
Democrat      593746
Republican    407985
Name: party, dtype: int64
(fec["contb_receipt_amt"] > 0).value_counts()  # 统计"contb_receipt_amt"列中大于0的值的数量和小于等于0的值的数量
True     991475
False     10256
Name: contb_receipt_amt, dtype: int64
fec = fec[fec["contb_receipt_amt"] > 0]  # 从fec DataFrame中筛选出"contb_receipt_amt"列大于0的行,并将结果重新赋值给fec变量

fec_mrbo = fec[fec["cand_nm"].isin(["Obama, Barack", "Romney, Mitt"])]  # 从fec DataFrame中筛选出"cand_nm"列包含"Obama, Barack"或"Romney, Mitt"的行,并将结果赋值给fec_mrbo变量

fec["contbr_occupation"].value_counts()[:10]  # 统计"contbr_occupation"列中各个职业的数量,并返回前10个结果
RETIRED                                   233990
INFORMATION REQUESTED                      35107
ATTORNEY                                   34286
HOMEMAKER                                  29931
PHYSICIAN                                  23432
INFORMATION REQUESTED PER BEST EFFORTS     21138
ENGINEER                                   14334
TEACHER                                    13990
CONSULTANT                                 13273
PROFESSOR                                  12555
Name: contbr_occupation, dtype: int64
occ_mapping = {
   "INFORMATION REQUESTED PER BEST EFFORTS" : "NOT PROVIDED",
   "INFORMATION REQUESTED" : "NOT PROVIDED",
   "INFORMATION REQUESTED (BEST EFFORTS)" : "NOT PROVIDED",
   "C.E.O.": "CEO"
}    # 映射 “前” 为 “后”

def get_occ(x):
    # 如果映射未提供,则返回 x
    return occ_mapping.get(x, x)

fec["contbr_occupation"] = fec["contbr_occupation"].map(get_occ)  # 使用 get_occ 函数将 "contbr_occupation" 列中的值进行映射
emp_mapping = {
   "INFORMATION REQUESTED PER BEST EFFORTS" : "NOT PROVIDED",
   "INFORMATION REQUESTED" : "NOT PROVIDED",
   "SELF" : "SELF-EMPLOYED",
   "SELF EMPLOYED" : "SELF-EMPLOYED",
}    # 映射 “前” 为 “后”

def get_emp(x):
    # 如果映射未提供,则返回 x
    return emp_mapping.get(x, x)

fec["contbr_employer"] = fec["contbr_employer"].map(get_emp)  # 使用 get_emp 函数将 "contbr_employer" 列中的值进行映射
by_occupation = fec.pivot_table("contb_receipt_amt",  # 使用"contb_receipt_amt"列作为值进行透视
                                index="contbr_occupation",  # 使用"contbr_occupation"列作为行索引
                                columns="party",  # 使用"party"列作为列索引
                                aggfunc="sum")  # 对值进行求和聚合

over_2mm = by_occupation[by_occupation.sum(axis="columns") > 2000000]  # 选择总和大于2000000的行并赋值给over_2mm变量

over_2mm  # 显示over_2mm DataFrame
party Democrat Republican
contbr_occupation
ATTORNEY 11141982.97 7477194.43
CEO 2074974.79 4211040.52
CONSULTANT 2459912.71 2544725.45
ENGINEER 951525.55 1818373.70
EXECUTIVE 1355161.05 4138850.09
HOMEMAKER 4248875.80 13634275.78
INVESTOR 884133.00 2431768.92
LAWYER 3160478.87 391224.32
MANAGER 762883.22 1444532.37
NOT PROVIDED 4866973.96 20565473.01
OWNER 1001567.36 2408286.92
PHYSICIAN 3735124.94 3594320.24
PRESIDENT 1878509.95 4720923.76
PROFESSOR 2165071.08 296702.73
REAL ESTATE 528902.09 1625902.25
RETIRED 25305116.38 23561244.49
SELF-EMPLOYED 672393.40 1640252.54
plt.figure()  # 创建一个新的空白Figure对象

over_2mm.plot(kind="barh")  # 对over_2mm DataFrame进行水平条形图的可视化

def get_top_amounts(group, key, n=5):
    totals = group.groupby(key)["contb_receipt_amt"].sum()  # 对group中的数据按key进行分组,计算每个组中"contb_receipt_amt"列的总和
    return totals.nlargest(n)  # 返回总和最大的前n个组

grouped = fec_mrbo.groupby("cand_nm")  # 根据候选人姓名对fec_mrbo DataFrame进行分组

grouped.apply(get_top_amounts, "contbr_occupation", n=7)  # 对分组后的数据应用get_top_amounts函数,按"contbr_occupation"列获取每个组的前7个最大总和

grouped.apply(get_top_amounts, "contbr_employer", n=10)  # 对分组后的数据应用get_top_amounts函数,按"contbr_employer"列获取每个组的前10个最大总和
cand_nm        contbr_employer                       
Obama, Barack  RETIRED                                   22694358.85
               SELF-EMPLOYED                             17080985.96
               NOT EMPLOYED                               8586308.70
               INFORMATION REQUESTED                      5053480.37
               HOMEMAKER                                  2605408.54
               SELF                                       1076531.20
               SELF EMPLOYED                               469290.00
               STUDENT                                     318831.45
               VOLUNTEER                                   257104.00
               MICROSOFT                                   215585.36
Romney, Mitt   INFORMATION REQUESTED PER BEST EFFORTS    12059527.24
               RETIRED                                   11506225.71
               HOMEMAKER                                  8147196.22
               SELF-EMPLOYED                              7409860.98
               STUDENT                                     496490.94
               CREDIT SUISSE                               281150.00
               MORGAN STANLEY                              267266.00
               GOLDMAN SACH & CO.                          238250.00
               BARCLAYS CAPITAL                            162750.00
               H.I.G. CAPITAL                              139500.00
Name: contb_receipt_amt, dtype: float64
bins = np.array([0, 1, 10, 100, 1000, 10000, 100_000, 1_000_000, 10_000_000])  # 定义一个包含分箱边界值的NumPy数组,用于将"contb_receipt_amt"列的值分成不同的区间

labels = pd.cut(fec_mrbo["contb_receipt_amt"], bins)  # 使用pd.cut函数根据指定的分箱边界将"contb_receipt_amt"列的值进行分箱,并返回一个包含对应区间标签的Series

labels  # 显示labels Series,即包含了每个"contb_receipt_amt"值所属的区间标签
411         (10, 100]
412       (100, 1000]
413       (100, 1000]
414         (10, 100]
415         (10, 100]
             ...     
701381      (10, 100]
701382    (100, 1000]
701383        (1, 10]
701384      (10, 100]
701385    (100, 1000]
Name: contb_receipt_amt, Length: 694282, dtype: category
Categories (8, interval[int64, right]): [(0, 1] < (1, 10] < (10, 100] < (100, 1000] < (1000, 10000] < (10000, 100000] < (100000, 1000000] < (1000000, 10000000]]
grouped = fec_mrbo.groupby(["cand_nm", labels])  # 根据候选人姓名和labels对fec_mrbo DataFrame进行分组

grouped.size().unstack(level=0)  # 计算每个候选人在不同区间标签下的数据量,并将结果转换为宽格式(以候选人姓名为列),并显示该结果
cand_nm Obama, Barack Romney, Mitt
contb_receipt_amt
(0, 1] 493 77
(1, 10] 40070 3681
(10, 100] 372280 31853
(100, 1000] 153991 43357
(1000, 10000] 22284 26186
(10000, 100000] 2 1
(100000, 1000000] 3 0
(1000000, 10000000] 4
plt.figure()  # 创建一个新的空白Figure对象

bucket_sums = grouped["contb_receipt_amt"].sum().unstack(level=0)  # 计算每个候选人在不同区间标签下的"contb_receipt_amt"列的总和,并转换为宽格式(以候选人姓名为列)

normed_sums = bucket_sums.div(bucket_sums.sum(axis="columns"), axis="index")  # 对每个候选人在不同区间标签下的总和进行归一化,以每行的总和作为除数,计算归一化后的比例

normed_sums  # 显示归一化后的比例结果

normed_sums[:-2].plot(kind="barh")  # 对前面除去最后两行的归一化比例结果进行水平条形图的可视化

grouped = fec_mrbo.groupby(["cand_nm", "contbr_st"])  # 根据候选人姓名和捐赠者所在州(contbr_st)对fec_mrbo DataFrame进行分组

totals = grouped["contb_receipt_amt"].sum().unstack(level=0).fillna(0)  # 计算每个候选人在不同州的总捐款金额,将结果转换为宽格式(以候选人姓名为列),并用0填充缺失值

totals = totals[totals.sum(axis="columns") > 100000]  # 筛选出总捐款金额超过100000的行

totals.head(10)  # 显示前10行的totals DataFrame
cand_nm Obama, Barack Romney, Mitt
contbr_st
AK 281840.15 86204.24
AL 543123.48 527303.51
AR 359247.28 105556.00
AZ 1506476.98 1888436.23
CA 23824984.24 11237636.60
CO 2132429.49 1506714.12
CT 2068291.26 3499475.45
DC 4373538.80 1025137.50
DE 336669.14 82712.00
FL 7318178.58 8338458.81
percent = totals.div(totals.sum(axis="columns"), axis="index")  # 对每个州的总捐款金额进行归一化,以每行的总和作为除数,计算归一化后的比例

percent.head(10)  # 显示前10行的percent DataFrame,即每个州的归一化比例结果
cand_nm Obama, Barack Romney, Mitt
contbr_st
AK 0.765778 0.234222
AL 0.507390 0.492610
AR 0.772902 0.227098
AZ 0.443745 0.556255
CA 0.679498 0.320502
CO 0.585970 0.414030
CT 0.371476 0.628524
DC 0.810113 0.189887
DE 0.802776 0.197224
FL 0.467417 0.532583

Q:什么是数据归一化和比例计算?

A:数据归一化(Data normalization)是一种常用的数据预处理技术,用于将不同特征或变量的取值范围进行统一,使其具有相同的尺度或比例。数据归一化的目的是消除不同特征之间的量纲差异,以便更好地进行数据分析和建模。

在上述代码中,我们对每个州的总捐款金额进行了归一化和比例计算:

首先对每行的数据进行求和,计算每个州的总捐款金额。使用`div()`函数将每个州的总捐款金额除以每行的总和。这样做的目的是将每个州的总捐款金额归一化,并计算出每个州的捐款金额占总捐款金额的比例。

        `totals.sum(axis="columns")`表示沿着行方向进行求和。
        `axis="index"`表示将每行的总和作为除数。

最后显示归一化和比例计算后的结果,即每个州的归一化比例结果。

物联沃分享整理
物联沃-IOTWORD物联网 » Python数据分析入门:四个简单示例带你轻松掌握Pandas

发表评论