将Unicode负号转换(来自matplotlib的刻度标签)
我在使用matplotlib时遇到了一个关于文本对象的问题,这个对象用来表示刻度标签。
为了测试,我需要检查在绘图中生成的刻度标签的值。如果标签是字符串或者正数,那就没问题:返回的是一个unicode字符串,我可以测试它(或者根据情况把它转换成数字),一切都很顺利。
但是如果标签是负数,我得到的却是一个我无法理解的乱码unicode字符串。
我们来看这个示例代码:
import pylab as plt
fig, ax = plt.subplots(1)
ax.plot([-1, 0, 1, 2], range(4))
labels = ax.get_xticklabels()
现在,如果我询问第二个标签的文本内容(也就是0
),我得到的是一个正常的unicode字符串:
labels[1].get_text()
# u'0.0'
但是第一个标签(-1
)的unicode却是个奇怪的东西。
labels[1].get_text()
# u'\u22121'
这个在终端中打印是正确的,但在这种情况下,我需要把它和一个数字值进行比较,而每次转换都失败,无论是用int
还是float
。
我尝试用以下代码把它转换成UTF-8字符串:
text = labels[1].get_text()
text.encode('utf8')
# '\xe2\x88\x921'
但结果还是可以正确打印,却在转换时出错。我也查看了unicodedata
模块,但它似乎只能转换单个字符,所以在这种情况下没什么用。我还尝试用unicodedata.normalize
来规范化字符串,试了各种可能的格式,但依然没有成功。
我转向了pipy模块unidecode
(正如在Python和字符规范化中建议的),但同样没有成功。
from unidecode import unidecode
unidecode(text)
# '[?]1'
我还尝试通过在Matplotlib中的非ASCII字符中提供的解决方案来避免字体问题,但结果还是一样(我不确定这是否应该有关系,因为这可能是可视化的问题……)。问题Matplotlib中的重音字符也有类似的问题,因为它关注的是可视化,而不是值本身。
我开始感到有点迷茫……我知道python 2.7在处理unicode时有一些“困难”,但通常我可以以某种方式避免这些问题。
我知道问题出在负号上,因为我可以通过简单替换来避免这个问题:
text.replace(u'\u2212', '-')
# u'-1'
但这更像是一个临时解决办法,而不是一个真正的解决方案,我几乎可以肯定这在不同系统上不稳定,所以我希望能找到一个更接近于解决方案的方法。
我正在使用:
- python 2.7.3
- matplotlib 1.2.0
- pylab 1.7.0
- IPython 0.13.1
在Kubuntu 12.10上。
非常感谢你的帮助!
编辑:
我纠正了绘图的顺序,因为我把x和y搞反了,抱歉。
编辑2:
类似的信息可以在这个链接找到:http://www.coniferproductions.com/2012/12/17/unicode-character-dump-in-python/
最后它显示在一些书中使用的负号是更美观的,但并没有被python解释器识别为有效字符。
编辑3:
谜底揭晓。matplotlib返回的字符是“负号”,也就是正确的排版符号。而键盘输入的实际上是“连字符-负号”,虽然常用但在排版上不正确。可以在维基百科上查看解释:http://en.wikipedia.org/wiki/Hyphen-minus。
所以,我使用的简单替换实际上是正确的做法,但“从伦理上讲”这是python(2.7和3.x都一样)中的一个bug,它没有识别负号的正确符号。
可以查看这个bug的跟踪记录:http://bugs.python.org/issue6632
编辑4:
要禁用这种行为,matplotlib有一个简单的解决方案,只需修改rcparams,无论是在.matplotlibrc文件中还是通过编程方式。
import matplotlib as mpl
mpl.rcParams['axes.unicode_minus']=False
2 个回答
用 plt.xticks()
来代替 ax.get_xticklabels()
:
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1)
ax.plot([-1, 0, 1, 2], range(4))
plt.savefig('/tmp/test.png')
loc, labels = plt.xticks()
print(type(loc))
# <type 'numpy.ndarray'>
print(loc)
# [-1. -0.5 0. 0.5 1. 1.5 2. ]
所有有效的unicode字符都有名字。我们可以查看这些名字,找出里面的数字词(DIGIT.keys()
),然后根据这些名字,把它们替换成“正常”的数字字符(DIGIT.values()
)来对应给定的unicode标签:
import matplotlib.pyplot as plt
import unicodedata as UD
DIGIT = {
'MINUS': u'-',
'ZERO': u'0',
'ONE': u'1',
'TWO': u'2',
'THREE': u'3',
'FOUR': u'4',
'FIVE': u'5',
'SIX': u'6',
'SEVEN': u'7',
'EIGHT': u'8',
'NINE': u'9',
'STOP': u'.'
}
def guess(unistr):
return ''.join([value for u in unistr
for key,value in DIGIT.iteritems()
if key in UD.name(u)])
fig, ax = plt.subplots(1)
ax.plot([-1, 0, 1, 2], range(4))
plt.savefig('/tmp/test.png')
labels = ax.get_xticklabels()
for label in labels:
label = label.get_text()
print(guess(label))
结果是
-1.0
-0.5
0.0
0.5
1.0
1.5
2.0