离线数据清洗,Spark和Python Pandas对比

13 篇文章 0 订阅
5 篇文章 0 订阅

导语

最近新学习了Spark中RDD的核心用法,为了巩固学习成果,于是使用Spark写了一个数据清洗的代码,正好之前使用过python中pandas对同样的数据做数据清洗,于是就把两种方式的代码都贴出来,做一个简单的对比

数据展示

豆瓣图书标签: 小说,[日] 东野圭吾 / 李盈春 / 南海出版公司 / 2014-5 / 39.50元,解忧杂货店,8.6,(297210人评价)
豆瓣图书标签: 文学,[哥伦比亚] 加西亚·马尔克斯 / 范晔 / 南海出版公司 / 2011-6 / 39.50元,百年孤独,9.2,(138353人评价)
豆瓣图书标签: 小说,[英] 肯·福莱特 / 于大卫 / 江苏凤凰文艺出版社 / 2016-5-1 / 129.80元,巨人的陨落,8.9,(39014人评价)
豆瓣图书标签: 小说,亦舒 / 新世界出版社 / 2007-8 / 22.00元,我的前半生,7.9,(22722人评价)
豆瓣图书标签: 小说,林奕含 / 北京联合出版公司 / 2018-1 / 45.00元,房思琪的初恋乐园,9.2,(23870人评价)
豆瓣图书标签: 小说,[美] 卡勒德·胡赛尼 / 李继宏 / 上海人民出版社 / 2006-5 / 29.00元,追风筝的人,8.9,(325801人评价)
豆瓣图书标签: 小说,[哥伦比亚] 加西亚·马尔克斯 / 范晔 / 南海出版公司 / 2011-6 / 39.50元,百年孤独,9.2,(138253人评价)
豆瓣图书标签: 小说,[哥伦比亚] 加西亚·马尔克斯 / 杨玲 / 南海出版公司 / 2012-9-1 / 39.50元,霍乱时期的爱情,9.0,(76618人评价)
豆瓣图书标签: 小说,[意] 埃莱娜·费兰特 / 陈英 / 人民文学出版社 / 2017-4 / 59.00元,新名字的故事,9.0,(8257人评价)
豆瓣图书标签: 小说,[俄] 维克托·阿斯塔菲耶夫 / 夏仲翼 等 / 理想国 | 广西师范大学出版社 / 2017-4 / 78.00元,鱼王,9.0,(1915人评价)
豆瓣图书标签: 小说,[美] 戴维·伽特森 / 熊裕 / 全本书店|作家出版社 / 2017-6 / 52.00元,雪落香杉树,8.4,(3397人评价)
豆瓣图书标签: 小说,[英] 毛姆 / 傅惟慈 / 上海译文出版社 / 2006-8 / 15.00元,月亮和六便士,9.0,(82482人评价)
豆瓣图书标签: 小说,[英] 肯·福莱特 / 陈杰 / 江苏凤凰文艺出版社 / 2017-3-1 / 132.00(全三册),世界的凛冬,8.9,(12271人评价)
豆瓣图书标签: 小说,余华 / 南海出版公司 / 1998-5 / 12.00元,活着,9.1,(153115人评价)
豆瓣图书标签: 小说,[英] 乔治·奥威尔 / 刘绍铭 / 北京十月文艺出版社 / 2010-4-1 / 28.00,1984,9.3,(49985人评价)
豆瓣图书标签: 小说,[美] 哈珀·李 / 高红梅 / 译林出版社 / 2012-9 / 32.00元,杀死一只知更鸟,9.2,(21417人评价)
豆瓣图书标签: 小说,钱锺书 / 人民文学出版社 / 1991-2 / 19.00,围城,8.9,(204184人评价)
...............

以上是爬取的豆瓣读书的书籍信息,保存为csv文件,字段名分别为图书标签,图书信息,书名,评分值,评分人数,因为是粗略爬取的,所以很多字段信息都是不明确的,不可以直接进行数据分析,需要对数据进行清洗,将数据转换为包含图书标签,作者,出版日期,价格,书名,评分值,评分人数等字段的结构化数据

Python Pandas代码

import pandas

if __name__ == '__main__':
    douban = pandas.read_csv('douban.csv', names=['tag', 'info', 'name', 'star', 'people'])
    douban = douban.drop_duplicates().reset_index(drop=True)
    infos = douban['info'].str.split('/')
    authors = []
    publish = []
    date = []
    si = 'sda'
    money = []
    country = []
    error = []
    for num in range(len(infos)):
        # money.append(one[-1])
        if len(infos[num]) >= 3:
            author_info = infos[num][0].strip()
            if len(author_info) > 3:
                if author_info.startswith('(') and author_info.__contains__(')'):
                    authors.append(author_info.split(')')[1])
                    country.append(author_info.split(")")[0].split("(")[1])
                    money.append(infos[num][-1].strip())
                    date.append(infos[num][-2].strip())
                elif author_info.startswith('[') and author_info.__contains__(']'):
                    authors.append(author_info.split(']')[1])
                    country.append(author_info.split("]")[0].split("[")[1])
                    money.append(infos[num][-1].strip())
                    date.append(infos[num][-2].strip())
                elif author_info.startswith('(') and author_info.__contains__(')'):
                    authors.append(author_info.split(')')[1])
                    country.append(author_info.split(")")[0].split("(")[1])
                    money.append(infos[num][-1].strip())
                    date.append(infos[num][-2].strip())
                elif author_info.startswith('【') and author_info.__contains__('】'):
                    authors.append(author_info.split('】')[1])
                    country.append(author_info.split("】")[0].split("【")[1])
                    money.append(infos[num][-1].strip())
                    date.append(infos[num][-2].strip())
                else:
                    error.append(num)
            else:
                country.append('中')
                authors.append(author_info)
                money.append(infos[num][-1].strip())
                date.append(infos[num][-2].strip())
        else:
            error.append(num)
    gudai = '唐宋元明清台台湾'
    country = ["中" if gudai.__contains__(x) else x for x in country]
    douban = douban.drop(index=error).reset_index(drop=True)
    douban['author'] = authors
    douban['money'] = money
    douban['country'] = country
    years = []
    for one in date:
        try:
            years.append(int(one.split("-")[0]))
        except:
            years.append(0)
    douban['year'] = years
    douban = douban[douban['year'] > 1800].reset_index(drop=True)
    douban['people'] = douban['people'].str.split('(').str[1].str.split("人").str[0]
    douban['tag'] = douban['tag'].str.split(':').str[1].str.strip()
    douban = douban.drop('info', axis=1)
    douban.to_csv("douban2.csv",index=None)

因为Pandas用的比较熟悉的缘故,所以写了65行代码就搞定了,并且优化优化可以缩减到50行以内,以下是数据清洗后的结果

小说,解忧杂货店,8.6,297210, 东野圭吾,39.50元,日,2014
文学,百年孤独,9.2,138353, 加西亚·马尔克斯,39.50元,哥伦比亚,2011
小说,巨人的陨落,8.9,39014, 肯·福莱特,129.80元,英,2016
小说,我的前半生,7.9,22722,亦舒,22.00元,中,2007
小说,房思琪的初恋乐园,9.2,23870,林奕含,45.00元,中,2018
小说,追风筝的人,8.9,325801, 卡勒德·胡赛尼,29.00元,美,2006
小说,百年孤独,9.2,138253, 加西亚·马尔克斯,39.50元,哥伦比亚,2011
小说,霍乱时期的爱情,9.0,76618, 加西亚·马尔克斯,39.50元,哥伦比亚,2012
小说,新名字的故事,9.0,8257, 埃莱娜·费兰特,59.00元,意,2017
小说,鱼王,9.0,1915, 维克托·阿斯塔菲耶夫,78.00元,俄,2017
小说,雪落香杉树,8.4,3397, 戴维·伽特森,52.00元,美,2017
小说,月亮和六便士,9.0,82482, 毛姆,15.00元,英,2006
小说,世界的凛冬,8.9,12271, 肯·福莱特,132.00(全三册),英,2017
小说,活着,9.1,153115,余华,12.00元,中,1998
小说,1984,9.3,49985, 乔治·奥威尔,28.00,英,2010
小说,杀死一只知更鸟,9.2,21417, 哈珀·李,32.00元,美,2012
小说,围城,8.9,204184,钱锺书,19.00,中,1991
小说,斯通纳,8.8,17304, 约翰·威廉斯,39.00元,美,2016
小说,囚鸟,8.0,1982, 库尔特·冯内古特,38.00元,美,2017

Spark代码

import org.apache.spark.{SparkConf, SparkContext}

object douban {
  def priArray(s:Array[String]): Unit ={
    for(x<-s){
      print(x)
      print(" ")
    }
    println()
  }
  def main(args: Array[String]): Unit = {
    val conf = new SparkConf().setMaster("local[1]").setAppName("douban")
    val sc = new SparkContext(conf)
    val douban = sc.textFile("douban.csv").zipWithIndex()//.map(x=>(x._2,x._1))
    val info = douban.map(x=>x._1.split(",")(1)+"/"+x._2.toString).map(removeSpace)
    val info_country= info.filter(x => x.contains("[") | x.contains("(") | x.contains("(") | x.contains("【"))
    val info_no_country = info.subtract(info_country).map(_.split("/")).filter(x => x.length>=4 &&x(0).length<=3)
    val info_country2 = info_country.map(_.split("/")).filter(x => x.length>=3)
    val split_Country = info_country2.map(splitCountry).filter(_.length>1).map(changeCountry)
    val add_Country = info_no_country.map(addCountry)
    val result = split_Country.union(add_Country).map(changeDate).filter(x=>x(2).toInt>0).map(x=>(x(x.length-1).toLong,x.toList.dropRight(1).mkString(",")))
    val douban2 = douban.map(x=>(x._2,x._1))
    val result2 = result.leftOuterJoin(douban2).map(_._2).map(x =>x._1+","+x._2.mkString)
    val result3 = result2.map(tagAndPeople)
    result3.saveAsTextFile("douban")
  }

  def tagAndPeople(s:String):String ={
    var resu = s.split(",")
    var tag = resu(4).split("\\:")(1).trim
    var peopleNum ="0"
    try{
      peopleNum = resu(resu.length-1).split("人")(0).split("\\(")(1)
    } catch{
      case ex:Exception=>
    }
    resu(4) = tag
    resu(resu.length-1) = peopleNum
    resu.mkString(",")
  }

  def changeDate(s:Array[String]):Array[String] = {
    var year = 0
    try{
      year = s(2).split("-")(0).toInt
    } catch{
      case ex:Exception=>
    }
    s(2) = year.toString
    s
  }

  def changeCountry(l:Array[String]):Array[String]={
    val gudai = "唐宋元明清台台湾"
    var subString = ""
    if (gudai.contains(l.head)) subString="中"
    var one = l(0)
    one match{
      case "美国" => subString = "美"
      case "日本" => subString = "日"
      case "英国" => subString = "英"
      case "俄罗斯" => subString = "俄"
      case "葡萄牙" => subString = "葡"
      case "冰岛" => subString = "冰"
      case _ => subString=one
    }
    if(!subString.equals("")){
      l(0) = subString
    }
    l
  }

  def addCountry(s:Array[String]):Array[String]={
    val author = s(0)
    val country = "中"
    val money = s(s.length - 2)
    val date = s(s.length - 3)
    var index = s(s.length-1).toString
    Array(country,author,date,money,index)
  }

  def splitCountry(s:Array[String]):Array[String]={
    var author = new String()
    var country = new String()
    var money = new String()
    var date = new String()
    var index =new String()
    if(s(0).startsWith("(")&s(0).contains(")")){
      author = s(0).split("\\)")(1)
      country = s(0).split("\\)")(0).split("\\(")(1)
      money = s(s.length-2)
      date = s(s.length-3)
      index = s(s.length-1).toString
    } else if (s(0).startsWith("[")&s(0).contains("]")){
      author = s(0).split(']')(1)
      country = s(0).split(']')(0).split('[')(1)
      money = s(s.length-2)
      date = s(s.length-3)
      index = s(s.length-1).toString
    } else if (s(0).startsWith("(")&s(0).contains(")")){
      author = s(0).split(')')(1)
      country = s(0).split(")")(0).split("(")(1)
      money = s(s.length-2)
      date = s(s.length-3)
      index = s(s.length-1).toString
    } else if (s(0).startsWith("【")&s(0).contains("】")){
      author = s(0).split("】")(1)
      country = s(0).split("】")(0).split("【")(1)
      money = s(s.length-2)
      date = s(s.length-3)
      index = s(s.length-1).toString
    } else{
      return Array("None")
    }
    Array(country,author,date,money,index)
  }

  def removeSpace(s:String):String={
    val s_li = s.split("/")
    var out = ""
    for(o <- s_li){
      if (s_li.indexOf(o)==s_li.length-1){
        out+=o.replace(" ","")
      }
      else{
        out+=o.replace(" ","")+"/"
      }
    }
    out
  }
}

Spark我用了131行,是Pandas的两倍,可能是才学的原因吧,当然了,Spark最在行的也不是离线数据清洗,而是基于Spark Streaming的实时数据清洗
以下是Spark清洗后的数据

散文,中,周耀辉,2013,32.00元,7749,7.4,699
随笔,中,陈丹青,2009,39,荒废集,8.1,9600
小说,英,劳伦斯,2004,24.00元,查特莱夫人的情人,7.6,5991
诗歌,法,伊夫·博纳富瓦,2017,52.00元,杜弗的动与静,8.7,108
小说,日,东野圭吾,2008,29.80元,白夜行,9.1,219833
文学,英,亨利·希金斯,2012,35.00元,真的不用读完一本书,6.8,226
文学,中,黄德海,2017,38.00元,书到今生读已迟,9.1,34
随笔,日,金子由纪子,2015,25.00元,不被理想束缚的生活,7,296
文学,西班牙,圣地亚哥·帕哈雷斯,2017,46,螺旋之谜,7.9,174
随笔,日,堺雅人,2014,30.00元,文·堺雅人:憧憬的日子,8.1,1728
小说,中,王小波,2006,19.80元,万寿寺,8.6,5761
小说,中,都梁,2005,28.00元,亮剑,8.9,13639
散文,日,永井荷风,2012,20.00元,晴日木屐,7.8,117
随笔,中,苗炜,2015,CNY36.00,面包会有的,7.9,1005

可以看出,除了字段的顺序不同外,与Pandas清洗的结果无异

总结

在Spark中,map,filter,reduce等函数的应用区别于传统编程流程化的处理方式,批处理,分布式,流处理等新的编程场景使得我们需要更多的函数对数据进行操作。在上述我自己写的Spark数据清洗代码中,131行代码就包含了6个字段处理的函数。

上述代码由于时间原因没有加注释,如果需要代码详细说明的话请私聊

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值