用一个csv格式的字符串创建dataframe对象

pandas提供了很多的方式去创建dataframe,例如接收字典,读取csv或excel格式文件。

在读取本地文件的时候,可以直接调用读取csv的方法,但是多数情况下,csv文件是作为一种模型的训练数据存储在hdfs上,pandas是没有办法直接读取hdfs上的数据的,需要通过hdfs客户端从hdfs上读取文件内容,再想办法转为pandas的dataframe。

  • 之前用的两种方式:
    1. 一种是读取hdfs的csv,然后写入本地文件,再利用pandas读取这个本地文件返回一个dataframe。
      import hdfs
      import pandas as pd
      
      
      hdfs_user = "root"
      hdfs_addr = "http://centos121:9870"
      cli = hdfs.InsecureClient(hdfs_addr, user=hdfs_user)
      hdfs_path = "/test/a.csv"
      
      
      with cli.read(hdfs_path, encoding='utf-8')as reader:
          res_list = list()
          for row in reader:
              res_list.append(row)
          with open("D:\\b.csv", 'w', encoding='utf-8')as f:
              f.writelines(res_list)
      df = pd.read_csv("D:\\b.csv")
      print(df)

       

    2. 第二种是读取hdfs的csv,然后存到一个列表中,然后逐行处理字符串,最终得到一个二维数组,然后将二维数组传给pandas得到一个dataframe。

      import hdfs
      import pandas as pd
      import re
      
      
      hdfs_user = "root"
      hdfs_addr = "http://centos121:9870"
      cli = hdfs.InsecureClient(hdfs_addr, user=hdfs_user)
      hdfs_path = "/test/a.csv"
      
      
      def split_by_dot_escape_quote(string):
          """
          按逗号分隔字符串,若其中有引号,将引号内容视为整体
          """
          # 匹配引号中的内容,非贪婪,采用正向肯定环视,
          # 当左引号(无论单双引)被匹配到,放入组quote,
          # 中间的内容任意,但是要用+?,非贪婪,且至少有一次匹配到字符,
          # 若*?,则匹配0次也可,并不会匹配任意字符(环视只匹配位置不匹配字符),
          # 由于在任意字符后面又限定了前面匹配到的quote,故只会匹配到",
          # +?则会限定前面必有字符被匹配,故"",或引号中任意值都可匹配到
          pattern = '(?=(?P<quote>[\'\"])).+?(?P=quote)'
          rs = re.finditer(pattern, string)
          for data in rs:
              # 匹配到的字符串
              old_str = data.group()
              # 将匹配到的字符串中的逗号替换为特定字符,
              # 以便还原到原字符串进行替换
              new_str = old_str.replace(',', '${dot}')
              # 由于匹配到的引号仅为字符串申明,并不具有实际意义,
              # 需要把匹配时遇到的引号都去掉,只替换掉当前匹配组的引号
              new_str = re.sub(data.group('quote'), '', new_str)
              string = string.replace(old_str, new_str)
          sps = string.split(',')
          return map(lambda x: x.replace('${dot}', ','), sps)
      
      
      def read_hdfs(hdfs_path, random_num_list=None):
          head_str = ''  # 表头
          res_list = []  # 表格数据
          with cli.read(hdfs_path, encoding='utf-8') as reader:
              index = 0
              for i, row_data in enumerate(reader):
                  if random_num_list is not None and len(random_num_list) <= 0:
                      break
                  if i == 0:  # 取第一行表头
                      head_str = re.sub('\r|\n|\t', '', row_data)
                  else:  # 去掉表头后的数据
                      index += 1  # 非空值数据当前行号
                      if random_num_list is not None and len(random_num_list) > 0 and index == random_num_list[0]:
                          res_list.append(re.sub('\r|\n|\t', '', row_data))
                          random_num_list.remove(random_num_list[0])
                      elif random_num_list is None:
                          res_list.append(re.sub('\r|\n|\t', '', row_data))
          src_list = list(map(split_by_dot_escape_quote, res_list))
          return pd.DataFrame(src_list, columns=head_str.split(','))
      
      
      print(read_hdfs(hdfs_path))
      

      第二种方式,大多数操作都是自己在处理每种情况,一是出错率较高,二是比较繁琐,由于代码水平有限,执行效率也不如pandas直接读取高。

  • 后来想到一种改良第一种方式的办法,不写入文件,只创建一个文件对象用来存储读到的内容,然后将这个文件对象传给pandas,事实证明这是可行的。因为本质上pandas读取csv得到的也是一个在内存中的文件对象,这样的话我就不需要再用正则去判断诸多可能导致创建df失败的情况了。

    from io import StringIO
    import hdfs
    import pandas as pd
    
    hdfs_user = "root"
    hdfs_addr = "http://centos121:9870"
    hdfs_path = "/test/a.csv"
    cli = hdfs.InsecureClient(hdfs_addr, user=hdfs_user)
    
    with cli.read(hdfs_path, encoding='utf-8')as reader:
        res_list = ''
        for row in reader:
            res_list += row
        pd.read_csv(StringIO(res_list))

    StringIO就是用来在内存中读写字符串,它会返回一个文件对象,正好可以传入pandas的read_csv,这样就实现了不需要和磁盘io的过程,减少了开销。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章