用一個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的過程,減少了開銷。

 

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