Преобразование знака минус-символа Юникода (из титлебелей matplotlib)
У меня проблема с объектом Text, который используется matplotlib для представления меток.
По причине тестирования мне нужно проверить значение меток тиков, созданных на графике. Если метка является строкой или положительным числом, нет никаких проблем: возвращается строка юникода, я проверяю ее (или конвертирую ее в число, учитывая обстоятельства), и все в порядке.
Но если метка является отрицательным числом, то я вернусь - это искаженная строка юникода по той причине, которую я не могу понять.
Возьмем этот пример кода:
import pylab as plt
fig, ax = plt.subplots(1)
ax.plot([-1, 0, 1, 2], range(4))
labels = ax.get_xticklabels()
теперь, если я задаю текстовое содержимое второй метки (0
), я получаю нормальную строку юникода:
labels[1].get_text()
# u'0.0'
но юникод первого (-1
) - странная вещь
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'
Я также пытался избежать проблем с шрифтами, используя решение в не-ASCII-символах в Matplotlib, но с тем же результатом (я не уверен, даже иметь что-то делать, будучи проблемой визуализации...). вопрос Акцентированные символы в Matplotlib имеет аналогичную проблему, поскольку он касается визуализации, а не самого значения
Я начинаю чувствовать себя немного потерянным... Я знаю, что у python 2.7 есть некоторая "трудность" юникода, но обычно я могу избежать их так или иначе.
Я знаю, что проблема - это знак минус, так как я могу избежать проблемы, используя грубую замену виновника:
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.
Большое спасибо за вашу помощь!
EDIT:
Исправлен порядок сюжета, так как я получил перевернутый x и y, извините
EDIT2:
аналогичная информация присутствует по этой ссылке: http://www.coniferproductions.com/2012/12/17/unicode-character-dump-in-python/
в конце он показывает, как в некоторых книгах знак минус используется более эстетически приятным, но не распознанным интерпретатором python как допустимый символ.
EDIT3:
Загадка решена. персонаж, возвращающий matplotlib, является "МИНУС СИГНАЛОМ", т.е. правильным типографическим знаком для минуса. Тот, который создает keybord, фактически является "HYPHEN-MINUS", который обычно используется, но не является типографически правильным. см. в wikipedia для объяснения http://en.wikipedia.org/wiki/Hyphen-minus.
Итак, простая замена, которую я использовал, на самом деле является правильной практической задачей, но "этически" является ошибкой в python (2.7 и 3.x), которые не распознают правильный символ для знака минус.
см. отслеживание ошибок в http://bugs.python.org/issue6632
EDIT4:
чтобы отключить это поведение, есть простое решение на matplotlib, просто измените rcparams либо в .matplotlibrc, либо программно.
import matplotlib as mpl
mpl.rcParams['axes.unicode_minus']=False
Ответы
Ответ 1
Все допустимые символы Юникода имеют имена. Мы можем проверить имя для распознанных числовых слов (DIGIT.keys()
) и на этом основании заменить "обычные" числовые символы (DIGIT.values()
) для данной метки юникода:
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
Ответ 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. ]