美文网首页
Python数据整洁(译)

Python数据整洁(译)

作者: RossH | 来源:发表于2019-03-18 23:34 被阅读0次

我最近发现了一篇Hidley Wickham在2014年发布的名为Tidy Data的论文,文中重点关注数据清理,数据整洁的一个方面:构建数据集以便于分析。 通过论文,Wickham演示了在分析之前如何以标准化方式构建任何数据集。 他详细介绍了不同类型的数据集以及如何将它们标准化。

作为一名数据科学家,我认为你应该非常熟悉这种数据集的标准化结构。 数据清理是数据科学中最常见的任务之一。 无论你正在处理何种数据或执行何种分析,你都必须在某个时刻清理数据。以标准格式整理数据可以使事情变得更轻松。你可以在不同的分析中重复使用一组标准工具。

在这篇文章中,我将总结Wickham在他的论文中使用的一些数据整洁的例子,我将演示如何使用Python pandas库进行操作。

定义整洁数据

Wickham定义为整洁的结构具有以下属性:

  • 每个变量(Variable)形成一列并有值(Value
  • 每个观察结果(Observation)形成一行
  • 每种类型的观察单元形成一个表格

一些定义:

  • 变量(Variable):度量或属性。身高,体重,性别等
  • 值(Value):实际测量值或属性。152厘米,80公斤,女性等
  • 观察结果(Observation):所有在同一单元上测量的值。每个人

脏数据集例子:

Treatment A Treatment B
John Smith - 2
Jane Doe 16 11
Mary Johnson 3 1

整洁数据集例子:

Name Treatment Result
John Smith a -
Jane Doe a 16
Mary Johnson a 3
John Smith b 2
Jane Doe b 11
Mary Johnson b 1

整理脏数据

通过从Wickham的论文中摘取的以下示例,我们将把凌乱的数据集变成整洁的格式。这里的目的不是分析数据集,而是在分析之前以标准化方式准备它们。以下是我们要解决的五种凌乱的数据集:

  • 列标题是值,而不是变量名。
  • 多种变量存储在一列中。
  • 变量同时存储在行和列中。
  • 多种类型的观察单元存储在同一个表中。
  • 单个观察单元存储在多个表中。

注意:本文中提供的所有代码均可在Github上获得。

列标题是值,而不是变量名

Pew研究中心数据集

该数据集探讨了收入与宗教之间的关系。

问题:列标题由可能的收入值组成。

import pandas as pd
import datetime
from os import listdir
from os.path import isfile, join
import glob
import re

df = pd.read_csv("./data/pew-raw.csv")
df
religion <$10k $10-20k $20-30k $30-40k $40-50k $50-75k
Agnostic 27 34 60 81 76 137
Atheist 12 27 37 52 35 70
Buddhist 27 21 30 34 33 58
Catholic 418 617 732 670 638 1116
Dont know/refused 15 14 15 11 10 35
Evangelical Prot 575 869 1064 982 881 1486
Hindu 1 9 7 9 11 34
Historically Black Prot 228 244 236 238 197 223
Jehovahs Witness 20 27 24 24 21 30
Jewish 19 19 25 25 30 95

此数据集中收入值不应该是列标题,而应该是income列里的值。为了整理这个数据集,我们需要整合(melt)它。 pandas库有一个内置函数,可以做到这一点。它将DataFrame从宽格式“展开”到长格式。后面我们也会重复使用到这个函数。

formatted_df = pd.melt(df,
                       ["religion"],
                       var_name="income",
                       value_name="freq")
formatted_df = formatted_df.sort_values(by=["religion"])
formatted_df.head(10)

这将输出整洁的数据集:

religion income freq
Agnostic <$10k 27
Agnostic $30-40k 81
Agnostic $40-50k 76
Agnostic $50-75k 137
Agnostic $10-20k 34
Agnostic $20-30k 60
Atheist $40-50k 35
Atheist $20-30k 37
Atheist $10-20k 27
Atheist $30-40k 52

Billboard Top 100数据集

此数据集包含Billboard周排名歌曲从进入前100开始到随后75周期间的变化。
问题:

  • 列标题由值组成:周数(x1st.week,...)
  • 如果歌曲在前100名中不到75周,则其余列将填充缺失值。
df = pd.read_csv("./data/billboard.csv", encoding="mac_latin2")
df.head(10)
year artist.inverted track time genre date.entered date.peaked x1st.week x2nd.week ...
2000 Destiny's Child Independent Women Part I 3:38 Rock 2000-09-23 2000-11-18 78 63.0 ...
2000 Santana Maria, Maria 4:18 Rock 2000-02-12 2000-04-08 15 8.0 ...
2000 Savage Garden I Knew I Loved You 4:07 Rock 1999-10-23 2000-01-29 71 48.0 ...
2000 Madonna Music 3:45 Rock 2000-08-12 2000-09-16 41 23.0 ...
2000 Aguilera, Christina Come On Over Baby (All I Want Is You) 3:38 Rock 2000-08-05 2000-10-14 57 47.0 ...
2000 Janet Doesn't Really Matter 4:17 Rock 2000-06-17 2000-08-26 59 52.0 ...
2000 Destiny's Child Say My Name 4:31 Rock 1999-12-25 2000-03-18 83 83.0 ...
2000 Iglesias, Enrique Be With You 3:36 Latin 2000-04-01 2000-06-24 63 45.0 ...
2000 Sisqo Incomplete 3:52 Rock 2000-06-24 2000-08-12 77 66.0 ...
2000 Lonestar Amazed 4:25 Country 1999-06-05 2000-03-04 81 54.0 ...

此数据集的整洁版本是周数没有作为列而是作为单个列的值。为此,我们将周数整合到单个列。我们将为每条记录每周创建一行。如果给定周没有数据,我们将不会为它创建一行。

# Melting
id_vars = ["year",
           "artist.inverted",
           "track",
           "time",
           "genre",
           "date.entered",
           "date.peaked"]

df = pd.melt(frame=df,id_vars=id_vars, var_name="week", value_name="rank")

# Formatting 
df["week"] = df['week'].str.extract('(\d+)', expand=False).astype(int)
df["rank"] = df["rank"].astype(int)

# Cleaning out unnecessary rows
df = df.dropna()

# Create "date" columns
df['date'] = pd.to_datetime(df['date.entered']) + pd.to_timedelta(df['week'], unit='w') - pd.DateOffset(weeks=1)

df = df[["year", 
         "artist.inverted",
         "track",
         "time",
         "genre",
         "week",
         "rank",
         "date"]]
df = df.sort_values(ascending=True, by=["year","artist.inverted","track","week","rank"])

# Assigning the tidy dataset to a variable for future usage
billboard = df

df.head(10)

整洁的数据集如下所示。歌曲详情仍有很多重复记录:曲目名称,时间和流派。因此,根据Wickham的定义,这个数据集仍然不够整洁。我们将在下一个例子中解决这个问题。

year artist.inverted track time genre week rank date
2000 2 Pac Baby Don't Cry (Keep Ya Head Up II) 4:22 Rap 1 87 2000-02-26
2000 2 Pac Baby Don't Cry (Keep Ya Head Up II) 4:22 Rap 2 82 2000-03-04
2000 2 Pac Baby Don't Cry (Keep Ya Head Up II) 4:22 Rap 3 72 2000-03-11
2000 2 Pac Baby Don't Cry (Keep Ya Head Up II) 4:22 Rap 4 77 2000-03-18
2000 2 Pac Baby Don't Cry (Keep Ya Head Up II) 4:22 Rap 5 87 2000-03-25
2000 2 Pac Baby Don't Cry (Keep Ya Head Up II) 4:22 Rap 6 94 2000-04-01
2000 2 Pac Baby Don't Cry (Keep Ya Head Up II) 4:22 Rap 7 99 2000-04-08
2000 2Ge+her The Hardest Part Of Breaking Up (Is Getting Ba... 3:15 R&B 1 91 2000-09-02
2000 2Ge+her The Hardest Part Of Breaking Up (Is Getting Ba... 3:15 R&B 2 87 2000-09-09
2000 2Ge+her The Hardest Part Of Breaking Up (Is Getting Ba... 3:15 R&B 3 92 2000-09-16

单表多类型

继续使用Billboard数据集,我们现在将解决前面提到的重复问题。
问题:

  • 单个表中有多个观察单元(song及其rank
    我们首先将创建一个表songs,其中包含每首歌曲的详细信息:
songs_cols = ["year", "artist.inverted", "track", "time", "genre"]
songs = billboard[songs_cols].drop_duplicates()
songs = songs.reset_index(drop=True)
songs["song_id"] = songs.index
songs.head(10)
year artist.inverted track time genre song_id
2000 2 Pac Baby Don't Cry (Keep Ya Head Up II) 4:22 Rap 0
2000 2Ge+her The Hardest Part Of Breaking Up (Is Getting Ba... 3:15 R&B 1
2000 3 Doors Down Kryptonite 3:53 Rock 2
2000 3 Doors Down Loser 4:24 Rock 3
2000 504 Boyz Wobble Wobble 3:35 Rap 4
2000 98� Give Me Just One Night (Una Noche) 3:24 Rock 5
2000 A*Teens Dancing Queen 3:44 Pop 6
2000 Aaliyah I Don't Wanna 4:15 Rock 7
2000 Aaliyah Try Again 4:03 Rock 8
2000 Adams, Yolanda Open My Heart 5:30 Gospel 9

然后我们将创建一个表ranks,仅包含song_iddaterank

ranks = pd.merge(billboard, songs, on=["year","artist.inverted", "track", "time", "genre"])
ranks = ranks[["song_id", "date","rank"]]
ranks.head(10)
song_id date rank
0 2000-02-26 87
0 2000-03-04 82
0 2000-03-11 72
0 2000-03-18 77
0 2000-03-25 87
0 2000-04-01 94
0 2000-04-08 99
1 2000-09-02 91
1 2000-09-09 87
1 2000-09-16 92

多个变量存在同一列

世界卫生组织的结核病记录

该数据集按国家,年份,年龄和性别记录确诊的结核病病例数。
问题:

  • 有些列包含多个值:性别和年龄。
  • 零和缺失值NaN的混合。 这是数据收集过程产生的,而区分数据对此数据集很重要。
df = pd.read_csv("./data/tb-raw.csv")
df
country year m014 m1524 m2534 m3544 m4554 m5564 m65 mu f014
AD 2000 0 0 1 0 0 0 0 NaN NaN
AE 2000 2 4 4 6 5 12 10 NaN 3
AF 2000 52 228 183 149 129 94 80 NaN 93
AG 2000 0 0 0 0 0 0 1 NaN 1
AL 2000 2 19 21 14 24 19 16 NaN 3
AM 2000 2 152 130 131 63 26 21 NaN 1
AN 2000 0 0 1 2 0 0 0 NaN 0
AO 2000 186 999 1003 912 482 312 194 NaN 247
AR 2000 97 278 594 402 419 368 330 NaN 121
AS 2000 NaN NaN NaN NaN 1 1 NaN NaN NaN

为了整理这个数据集,我们需要从标题中删除不同的值并将它们展开成行。 我们首先需要将sex + age group列整合为单列。整合后,我们将从中派生出三列:sexage_lowerage_upper。有了这些,我们就能够正确地构建一个整洁的数据集。

df = pd.melt(df, id_vars=["country","year"], value_name="cases", var_name="sex_and_age")

# Extract Sex, Age lower bound and Age upper bound group
tmp_df = df["sex_and_age"].str.extract("(\D)(\d+)(\d{2})")    

# Name columns
tmp_df.columns = ["sex", "age_lower", "age_upper"]

# Create `age`column based on `age_lower` and `age_upper`
tmp_df["age"] = tmp_df["age_lower"] + "-" + tmp_df["age_upper"]

# Merge 
df = pd.concat([df, tmp_df], axis=1)

# Drop unnecessary columns and rows
df = df.drop(['sex_and_age',"age_lower","age_upper"], axis=1)
df = df.dropna()
df = df.sort(ascending=True,columns=["country", "year", "sex", "age"])
df.head(10)

执行后得出整洁的数据集。

country year cases sex age
AD 2000 0 m 0-14
AD 2000 0 m 15-24
AD 2000 1 m 25-34
AD 2000 0 m 35-44
AD 2000 0 m 45-54
AD 2000 0 m 55-64
AE 2000 3 f 0-14
AE 2000 2 m 0-14
AE 2000 4 m 15-24
AE 2000 4 m 25-34

变量同时在行和列

全球历史气候网络数据集

该数据集包含2010年墨西哥气象站(MX17004)五个月的每日天气记录。
问题:

  • 变量存储在行(tmintmax)和列(days)中。
df = pd.read_csv("./data/weather-raw.csv")
df
id year month element d1 d2 d3 d4 d5 d6 d7 d8
MX17004 2010 1 tmax NaN NaN NaN NaN NaN NaN NaN NaN
MX17004 2010 1 tmin NaN NaN NaN NaN NaN NaN NaN NaN
MX17004 2010 2 tmax NaN 27.3 24.1 NaN NaN NaN NaN NaN
MX17004 2010 2 tmin NaN 14.4 14.4 NaN NaN NaN NaN NaN
MX17004 2010 3 tmax NaN NaN NaN NaN 32.1 NaN NaN NaN
MX17004 2010 3 tmin NaN NaN NaN NaN 14.2 NaN NaN NaN
MX17004 2010 4 tmax NaN NaN NaN NaN NaN NaN NaN NaN
MX17004 2010 4 tmin NaN NaN NaN NaN NaN NaN NaN NaN
MX17004 2010 5 tmax NaN NaN NaN NaN NaN NaN NaN NaN
MX17004 2010 5 tmin NaN NaN NaN NaN NaN NaN NaN NaN

为了使数据集整洁,我们需要将三个错位变量(tmintmaxdays)作为三个单独的列:tmintmaxdate

# Extracting day
df["day"] = df["day_raw"].str.extract("d(\d+)", expand=False)  
df["id"] = "MX17004"

# To numeric values
df[["year","month","day"]] = df[["year","month","day"]].apply(lambda x: pd.to_numeric(x, errors='ignore'))

# Creating a date from the different columns
def create_date_from_year_month_day(row):
    return datetime.datetime(year=row["year"], month=int(row["month"]), day=row["day"])

df["date"] = df.apply(lambda row: create_date_from_year_month_day(row), axis=1)
df = df.drop(['year',"month","day", "day_raw"], axis=1)
df = df.dropna()

# Unmelting column "element"
df = df.pivot_table(index=["id","date"], columns="element", values="value")
df.reset_index(drop=False, inplace=True)
df
id date tmax tmin
MX17004 2010-02-02 27.3 14.4
MX17004 2010-02-03 24.1 14.4
MX17004 2010-03-05 32.1 14.2

同个观察对象存于多表

数据集:2014/2015年伊利诺伊州男婴姓名。

问题:

  • 数据分布在多个表/文件中。
  • “Year”变量出现在文件名中。

为了将这些不同的文件加载到单个DataFrame中,我们可以运行自定义脚本将文件附加到一起。此外,我们还需要从文件名中提取“Year”变量。

def extract_year(string):
    match = re.match(".+(\d{4})", string) 
    if match != None: return match.group(1)
    
path = './data'
allFiles = glob.glob(path + "/201*-baby-names-illinois.csv")
frame = pd.DataFrame()
df_list= []
for file_ in allFiles:
    df = pd.read_csv(file_,index_col=None, header=0)
    df.columns = map(str.lower, df.columns)
    df["year"] = extract_year(file_)
    df_list.append(df)
    
df = pd.concat(df_list)
df.head(5)
rank name frequency sex year
1 Noah 837 Male 2014
2 Alexander 747 Male 2014
3 William 687 Male 2014
4 Michael 680 Male 2014
5 Liam 670 Male 2014

总结

在这篇文章中,我专注于Wickham论文的数据操作部分。我的主要目的是在Python中演示数据操作。值得一提的是,他的论文中有一个很重要的部分提到了通过整理数据集可以从中受益的工具和可视化。在这篇文章中我没有涵盖此部分内容。

总的来说,我乐于准备这篇文章并将数据集整理成简洁的格式。这种格式使查询和过滤数据变得更加容易。这种方法可以在分析中更轻松地重用库和代码。它还可以更轻松地与其他数据分析人员共享数据集。


原文链接:Tidy Data in Python

相关文章

网友评论

      本文标题:Python数据整洁(译)

      本文链接:https://www.haomeiwen.com/subject/imrumqtx.html