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
我是一个相当新的 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