Python/Pandas:迁移的 .CSV 数据清理

Python/Pandas: .CSV Data Cleanup For Migration

我是一个相当新的 python/pandas 用户,我的任务是清理大约 5,000 行 csv 记录,然后将这些记录迁移到 sql 数据库中.

内容是个人的个人信息(这让我无法发布以供参考)和他们的 'seat' 职业信息,但是该文件一直...管理不善...多年来,并且已经最终看起来像这样:

#Sect1         Sect2        Sect3        Seat#
L/L/L/L   320/320/319/321  D/C/D/C   1-2/1-2/1-2/1-2
  V             602           -            1-6
  T             101           F            1&3
  R             158           -            3* 4
  U             818           4            Ds9R

那个人的个人信息在左边没有显示的四列中。

实际上,即使只是上面选择的第一行实际上也应该是:

    #Sect1     Sect2   Sect3   Seat#
      L         320      D       1
      L         320      D       2
      L         320      C       1
      L         320      C       2
      L         319      D       1
      L         319      D       2
      L         321      C       1
      L         321      C       2
    

“-”表示它是 'through' 而不是 'and'。 (例如;在我的原始示例中,第二行是 Seat# 1 through Seat# 6,而不是 Seat# 1 和 6。

我还应该注意,这些人没有唯一的 ID/Index,它完全基于 First/Last 名字。

我一直在尝试打破其中的一些,但在

方面取得的成功有限
df1 = df1.drop('Sect2', axis=1).join(df1['Sect2'].str.split('/', expand=True).stack().reset_index(level=1, drop=True).rename('Sect2'))

但这最终会创建错误的记录,例如

#Sect1     Sect2   Sect3   Seat#
  L         319      C       1

最后,我的问题是;甚至可以使用脚本来清理这些数据吗?我很快 运行 就没主意了,真的不想手动执行此操作,但我也不想再浪费时间尝试编写脚本,如果这是毫无意义的尝试。

下面的代码应该可以解决您 post 中描述的两个问题。应该有足够的选择性以避免误解行,但可能仍然需要一些手动管理。

基本概念是逐行迭代,在继续之前尽可能多地处理。首要任务是拆分包含字符“/”的行。如果找到 none,则解释范围值“-”。 while 循环允许逐步改进。例如,代码会将 1-3 转换为 1/2/3,然后重新读取同一行并将其拆分为 3 个不同的行。

# build demo dataframe
d = {"Sect1": ["L/L/L/L", "V", "T", "R", "U"], 
     "Sect2": ["320/320/319/321", "602", "101", "158", "818"],
     "Sect3": ["D/C/D/C", "-", "F", "-", "4"],
     "Seat#": ["1-2/1-2/1-2/1-2", "1-6", "1&3", "3* 4", "Ds9R"]}
df = pd.DataFrame(data=d)


index = 0
while index < len(df):
    len_df = len(df)
    row_li = [df.iloc[index][x] for x in df.head()]
    # extract separated values
    sep_li = [x.split("/") for x in row_li]
    sep_min, sep_max = len(min(sep_li, key=lambda x: len(x))), len(max(sep_li, key=lambda x: len(x)))
    # extract range values
    num_range_li = [re.findall("^\d+\-\d+$|$", x)[0].split("-") for x in row_li]
    num_range_max = len(max(num_range_li, key=lambda x: len(x)))
    # create temporary dictionary representing current row
    r = {}
    for i, head in enumerate(df.head()):
        r[head] = row_li[i]
    # separated values treatment -> split into distinct rows
    if sep_min > 1 and sep_min == sep_max:
        for i, head in enumerate(df.head()):
            r[head] = sep_li[i]
        row_df = pd.DataFrame(data=r)
        df = df.append(row_df, ignore_index=True)
    # range values treatment -> convert into separated values
    elif num_range_max > 1:
        for part in (1, 2):
            for idx, header in enumerate(df.head()):
                if len(num_range_li[idx]) > 1:
                    split_li = [str(x) for x in range(int(num_range_li[idx][0]), int(num_range_li[idx][1])+1)]
                    # convert range values to separated values
                    if part == 1:
                        r[header] = "/".join(split_li)
                    # multiply other values
                    else:
                        for i, head in enumerate(df.head()):
                            if i != idx:
                                r[head] = "/".join([r[head] for x in range(len(split_li))])
        row_df = pd.DataFrame(data=r, index=[0])
        df = df.append(row_df, ignore_index=True)

    # if no new rows are added, increment
    if len(df) == len_df:
        index += 1
    # if rows are added, drop current row
    else:
        df = df.iloc[:index].append(df.iloc[index+1:])

print(df)

输出

   Sect1 Sect2 Sect3 Seat#
0      T   101     F   1&3
1      R   158     -  3* 4
2      U   818     4  Ds9R
4      V   602     -     1
5      V   602     -     2
6      V   602     -     3
7      V   602     -     4
8      V   602     -     5
9      V   602     -     6
10     L   320     D     1
11     L   320     D     2
12     L   320     C     1
13     L   320     C     2
14     L   319     D     1
15     L   319     D     2
16     L   321     C     1
17     L   321     C     2