pandas csv 写入的注意点 | quoting 参数 | 它是如何处理和还原带有特殊符号的字段的. | 比特填充和奇数引号
你在制作一个数据集,这个数据集你跑了一个晚上,结果在写入的时候,没有注意特殊符号,导致因为特殊符号和分割符号冲突,导致整个 csv 读入的时候乱了,这时候你既不像浪费时间修复,也不想再跑一遍.
为了避免上面情况的发生,我有必要了解一下 pandas.write_csv()对一些特殊情况的处理方式.
quoting 参数 | 它是 pandas 对你最后的仁慈.
实际上上面的情况很难发生,因为 pandas 写入的时候,默认使用csv.QUOTE_MINIMAL
,它会用引号包裹类似,
含有这样的分割符号的字段.
这是一些例子:
CHAPTER 5. The Egotist Becomes a Personage,第5章 自大狂成为名人
"Fitzgerald and his wife Zelda, soon after publication",菲茨杰拉德和妻子泽尔达,出版后不久
BOOK ONE — The Romantic Egotist,第一册 - 浪漫的自我陶醉者
"CHAPTER 1. Amory, Son of Beatrice",第1章 比阿特丽斯之子阿莫里
这里的引号就是csv.QUOTE_MINIMAL
的作用.
它并不是语料里面原来就有的,而是 pandas 写入的时候自动加上的.
它至少避免了你的 csv 文件在读取的时候出现问题.
但是这里衍生出来一些问题:
- 如果语料中本来就具有引号,这个引号会被怎么处理?
- 如果语料中不希望具有引号,那么我们在不知道的时候被写入了这个引号是否会被读取,会不会对我们的数据造成影响?能不能做到写入即读取(两者保持一致)?
- 哪些符号会被视作特殊符号,被引号包裹?
一个一个看.
引号的处理
首先要了解的是,我们用的是 pandas 写入 csv,那么我们首先就得在 python 中保留我们的引号.
默认引号会和字符串的引号冲突,所以我们需要转义.
import pandas as pd
speaker = "Steve"
example = "\"Hello World.\""
df = pd.DataFrame({"speaker": [speaker], "example": [example]})
df.to_csv("example.csv", index=False)
# 它默认是 csv.QUOTE_MINIMAL
# csv.QUOTE_MINIMAL (默认): 只在必要时 (当字段包含逗号、引号等特殊字符时) 才使用引号。
# csv.QUOTE_ALL: 对所有字段都使用引号。
# csv.QUOTE_NONE: 不使用引号。
# csv.QUOTE_NONNUMERIC: 对非数字的字段使用引号。
按照默认的参数,它写入的文件是长这样的:
speaker,example
Steve,"""Hello World."""
它似乎加了两层引号,但我们不关心源文件,我们之关心读取后长什么样,所以我们读取一下:
import pandas as pd
df = pd.read_csv("example.csv")
print(df)
似乎是符合预期的:
(base) xnne@xnne-PC:~/math-x-science/school-homework$ /home/xnne/miniconda3/envs/nlp/bin/python /home/xnne/math-x-science/school-homework/test.py
speaker example
0 Steve "Hello World."
也就是说,我们可以尽情地使用引号.pandas 会在写入的时候先把引号当作特殊符号包裹,然后再还原.
特殊符号的处理
从上一个可以看到特殊符号会在写入的时候被包裹,但是包裹后,实际上读取的时候又会脱掉引号.写入即读取.
这里我们也验证一下:
import pandas as pd
speaker = "Steve"
example = "Hello,World."
df = pd.DataFrame({"speaker": [speaker], "example": [example]})
df.to_csv("example.csv", index=False)
写入后是这样的:
speaker,example
Steve,"Hello,World."
读取:
(base) xnne@xnne-PC:~/math-x-science/school-homework$ /home/xnne/miniconda3/envs/nlp/bin/python /home/xnne/math-x-science/school-homework/test.py
speaker example
0 Steve Hello,World.
wc 数据还原了.
依然是写入即读取.
我在写的时候思考了一下为啥可以做到区分我们写的引号和由于 quoting 被动引入的引号,明明它们的编码是一样的.
原因推测:(比特填充原理)
在 MAC 数据帧(二进制)封装的时候,开头还是哪里似乎总是有连续的 1,假设它是 6 位吧.
它会根据这个连续的 0 判断是不是 MAC 帧的起始.
但那个里面就会有一个问题,就是如果接收数据里面也有连续的 0,那么就会被判断为帧的起始.
而它是这么解决的,对于发送方,对数据部分做这样的处理,只要碰到连续 5 个 0,就在后面填充一个 1,这个不一定要 5 个,可以自定义,但如果高于我上面假设的字段长度可想而知会出现问题.
对于接收方,也对数据做相同的反处理,只要碰到连续的 5 个 0,就去掉后面的一个 1.
你可能会疑惑,0000010
数据会不会被混淆,不会,因为发送方和接收方做的处理是一致的,如果数据是0000010
,那么发送出去的数据会是这样:
00000110
.
而接收方再同样反处理一下,依然可以得到000010
,可以说是相当高明的处理方式.
所以我推测,这里的引号,处理规则应该也和我那个类似,那么就可以来钻一下漏洞了.
因为根据那个原理,如果连续的引号输入进去,应该就会出现一些问题,让我们尝试一下.
import pandas as pd
speaker = "Steve"
example = "\"\"\"Hello World.\"\"\""
df = pd.DataFrame({"speaker": [speaker], "example": [example]})
df.to_csv("example.csv", index=False)
结果:
speaker,example
Steve,"""""""Hello World."""""""
我已经确定它怎么处理我们数据中的引号了.
很贱,它把我们的引号转成两重引号,然后再自己加上一重引号.
结果就是,csv 内引号重数总是奇数.
那样子,就可以做到读取的时候先把引号-1 然后 /2 恢复出来原来的引号.
读取后:
(base) xnne@xnne-PC:~/math-x-science/school-homework$ /home/xnne/miniconda3/envs/nlp/bin/python /home/xnne/math-x-science/school-homework/test.py
speaker example
0 Steve """Hello World."""
我找不到漏洞.
所以,to_csv
默认参数随便干就可以.
总结
一些很细节的东西都可以用来做成一个漂亮的算法,比如比特填充,比如奇数引号.
很神奇啊.
差点忘了
哪些符号是特殊符号呢?
被我们设置成分割符号的符号,比如默认,
,这样看来实际上也没必要去改.
因为它会自动处理.
以及我们的"
,它会被处理成""
.
其他的不重要,躺平用就好了.