Ответ на вопрос, заданный на koldunov.net
Импортируем модуль numpy и включаем отображение графиков внутри ноутбука (понадобится нам позже)
import numpy
%pylab inline
Welcome to pylab, a matplotlib-based Python environment [backend: module://IPython.kernel.zmq.pylab.backend_inline]. For more information, type 'help(pylab)'.
Название файла, который мы будем считывать "sample.txt"
ifile = open('sample.txt', 'r')
lines = ifile.readlines()
Так выглядит прочитанный нами список со строками. Во вставленном вами примере не все строки были одинаковой длинны. Так строка для 1977 года заканчивалась (\n) прямо после обозначения года. Надеюсь в ваших оригинальных данных это не так, иначе для того чтобы пример работал придётся вручную выравнивать все строки :)
lines
['20674 1967 22.0 35.0 28.0 39.0 22.0 23.0 59.0 46.0 49.0 45.0 36.0 29.0\n', '20674 1968 17.0 21.0 28.0 17.0 14.0 25.0 47.0 68.0 22.0 22.0\n', '20674 1969 20.0 16.0 15.0 8.0 9.0 17.0 60.0 33.0 27.0 11.0 27.0 30.0\n', '20674 1970 11.0 17.0 24.0 10.0 19.0 14.0 56.0 10.0 24.0 25.0 36.0 43.0\n', '20674 1971 91.0 39.0 17.0 4.0 12.0 13.0 31.0 67.0 78.0 16.0 31.0 26.0\n', '20674 1972 32.0 47.0 11.0 28.0 19.0 23.0 51.0 43.0 34.0 35.0 4.0 69.0\n', '20674 1973 25.0 36.0 25.0 9.0 38.0 50.0 21.0 75.0 43.0 36.0 16.0 15.0\n', '20674 1974 26.0 40.0 30.0 21.0 44.0 31.0 44.0 22.0 61.0 32.0 27.0\n', '20674 1975 49.0 16.0 15.0 29.0 12.0 28.0 35.0 25.0 35.0 43.0 24.0 54.0\n', '20674 1976 24.0 34.0 17.0 36.0 19.0 53.0 40.0 63.0 47.0 19.0 21.0 14.0\n', '20674 1977 \n', '20674 1978 20.0 20.0 16.0 6.0 14.0 18.0 48.0 41.0 12.0 27.0 25.0 16.0\n', '20674 1979 22.0 10.0 9.0 8.0 48.0 38.0 34.0 24.0 13.0 19.0 26.0\n', '20674 1980 21.0 35.0 9.0 14.0 23.0 41.0 66.0 67.0 55.0 44.0 8.0 32.0\n', '20674 1981 105.0 14.0 14.0 23.0 40.0 14.0 47.0 27.0 26.0 28.0 \n', '20674 1982 8.0 13.0 13.0 24.0 12.0 58.0 13.0 71.0 40.0 23.0 5.0 26.0\n']
Так выглядит строка с пропусками:
lines[1]
'20674 1968 17.0 21.0 28.0 17.0 14.0 25.0 47.0 68.0 22.0 22.0\n'
Мы естественно можем считать этот пропуск:
lines[1][59:62]
' '
Засунуть его в переменную:
a = lines[1][59:62]
И проверить может ли питон использовать для сравнения просто пробелы, без всяких \t
a == ' '
True
Может :)
Теперь давайте поймём сколько у нас строк, то есть лет:
len(lines)
16
И создадим пустой numpy массив:
data = numpy.empty((16,12))
Поскольку мы сначала будем работать с данными в строковом формате, и мы знаем, что каждое строковое значение будет состоять из 4 символов, мы сперва заполняем массив цифрами состоящими как минимум из 4 символов (любыми). Затем меняем формат данных на строковый. Четвёрка после S означает количество символов в каждом элементе массива.
data.fill(9999)
data = data.astype('|S4' )
Так сейчас выглядят наш массив:
data
array([['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999'], ['9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999', '9999']], dtype='|S4')
Теперь заполняем наш массив из файла, исходя из предположения, что положение значений в строках неизменно:
for year, line in enumerate(lines):
data[year,0] = line[12:16]
data[year,1] = line[18:22]
data[year,2] = line[24:28]
data[year,3] = line[30:34]
data[year,4] = line[36:40]
data[year,5] = line[42:46]
data[year,6] = line[48:52]
data[year,7] = line[54:58]
data[year,8] = line[60:64]
data[year,9] = line[66:70]
data[year,10] = line[72:76]
data[year,11] = line[78:82]
Теперь массив выглядит вот так:
data
array([['22.0', '35.0', '28.0', '39.0', '22.0', '23.0', '59.0', '46.0', '49.0', '45.0', '36.0', '29.0'], ['17.0', '21.0', '28.0', '17.0', '14.0', '25.0', '47.0', '68.0', ' ', ' ', '22.0', '22.0'], ['20.0', '16.0', '15.0', ' 8.0', ' 9.0', '17.0', '60.0', '33.0', '27.0', '11.0', '27.0', '30.0'], ['11.0', '17.0', '24.0', '10.0', '19.0', '14.0', '56.0', '10.0', '24.0', '25.0', '36.0', '43.0'], ['91.0', '39.0', '17.0', ' 4.0', '12.0', '13.0', '31.0', '67.0', '78.0', '16.0', '31.0', '26.0'], ['32.0', '47.0', '11.0', '28.0', '19.0', '23.0', '51.0', '43.0', '34.0', '35.0', ' 4.0', '69.0'], ['25.0', '36.0', '25.0', ' 9.0', '38.0', '50.0', '21.0', '75.0', '43.0', '36.0', '16.0', '15.0'], ['26.0', '40.0', '30.0', '21.0', '44.0', '31.0', '44.0', '22.0', '61.0', '32.0', ' ', '27.0'], ['49.0', '16.0', '15.0', '29.0', '12.0', '28.0', '35.0', '25.0', '35.0', '43.0', '24.0', '54.0'], ['24.0', '34.0', '17.0', '36.0', '19.0', '53.0', '40.0', '63.0', '47.0', '19.0', '21.0', '14.0'], [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '], ['20.0', '20.0', '16.0', ' 6.0', '14.0', '18.0', '48.0', '41.0', '12.0', '27.0', '25.0', '16.0'], ['22.0', ' ', '10.0', ' 9.0', ' 8.0', '48.0', '38.0', '34.0', '24.0', '13.0', '19.0', '26.0'], ['21.0', '35.0', ' 9.0', '14.0', '23.0', '41.0', '66.0', '67.0', '55.0', '44.0', ' 8.0', '32.0'], ['05.0', '14.0', '14.0', '23.0', ' ', '40.0', '14.0', '47.0', '27.0', '26.0', '28.0', ' '], [' 8.0', '13.0', '13.0', '24.0', '12.0', '58.0', '13.0', '71.0', '40.0', '23.0', ' 5.0', '26.0']], dtype='|S4')
Нам нужно как то заменить пробелы. numpy позволяет применить векторизованную нотацию, так что нам не нужно гнать цикл через весь массив. Выражение ниже значит - заменить все элементы равные 4 пробелам на -999 :
data[data==' '] = '-999'
data
array([['22.0', '35.0', '28.0', '39.0', '22.0', '23.0', '59.0', '46.0', '49.0', '45.0', '36.0', '29.0'], ['17.0', '21.0', '28.0', '17.0', '14.0', '25.0', '47.0', '68.0', '-999', '-999', '22.0', '22.0'], ['20.0', '16.0', '15.0', ' 8.0', ' 9.0', '17.0', '60.0', '33.0', '27.0', '11.0', '27.0', '30.0'], ['11.0', '17.0', '24.0', '10.0', '19.0', '14.0', '56.0', '10.0', '24.0', '25.0', '36.0', '43.0'], ['91.0', '39.0', '17.0', ' 4.0', '12.0', '13.0', '31.0', '67.0', '78.0', '16.0', '31.0', '26.0'], ['32.0', '47.0', '11.0', '28.0', '19.0', '23.0', '51.0', '43.0', '34.0', '35.0', ' 4.0', '69.0'], ['25.0', '36.0', '25.0', ' 9.0', '38.0', '50.0', '21.0', '75.0', '43.0', '36.0', '16.0', '15.0'], ['26.0', '40.0', '30.0', '21.0', '44.0', '31.0', '44.0', '22.0', '61.0', '32.0', '-999', '27.0'], ['49.0', '16.0', '15.0', '29.0', '12.0', '28.0', '35.0', '25.0', '35.0', '43.0', '24.0', '54.0'], ['24.0', '34.0', '17.0', '36.0', '19.0', '53.0', '40.0', '63.0', '47.0', '19.0', '21.0', '14.0'], ['-999', '-999', '-999', '-999', '-999', '-999', '-999', '-999', '-999', '-999', '-999', '-999'], ['20.0', '20.0', '16.0', ' 6.0', '14.0', '18.0', '48.0', '41.0', '12.0', '27.0', '25.0', '16.0'], ['22.0', '-999', '10.0', ' 9.0', ' 8.0', '48.0', '38.0', '34.0', '24.0', '13.0', '19.0', '26.0'], ['21.0', '35.0', ' 9.0', '14.0', '23.0', '41.0', '66.0', '67.0', '55.0', '44.0', ' 8.0', '32.0'], ['05.0', '14.0', '14.0', '23.0', '-999', '40.0', '14.0', '47.0', '27.0', '26.0', '28.0', '-999'], [' 8.0', '13.0', '13.0', '24.0', '12.0', '58.0', '13.0', '71.0', '40.0', '23.0', ' 5.0', '26.0']], dtype='|S4')
Однако элементы в массиве всё ещё имеют тип str, что нас, возможно не очень устраивает, особенно в случае, если мы хотим что-то посчитать. Перевести строки в числа с плавающей запятой (или в нашем случае точкой :)) можно при помощи команды astype:
data = data.astype('float')
data
array([[ 22., 35., 28., 39., 22., 23., 59., 46., 49., 45., 36., 29.], [ 17., 21., 28., 17., 14., 25., 47., 68., -999., -999., 22., 22.], [ 20., 16., 15., 8., 9., 17., 60., 33., 27., 11., 27., 30.], [ 11., 17., 24., 10., 19., 14., 56., 10., 24., 25., 36., 43.], [ 91., 39., 17., 4., 12., 13., 31., 67., 78., 16., 31., 26.], [ 32., 47., 11., 28., 19., 23., 51., 43., 34., 35., 4., 69.], [ 25., 36., 25., 9., 38., 50., 21., 75., 43., 36., 16., 15.], [ 26., 40., 30., 21., 44., 31., 44., 22., 61., 32., -999., 27.], [ 49., 16., 15., 29., 12., 28., 35., 25., 35., 43., 24., 54.], [ 24., 34., 17., 36., 19., 53., 40., 63., 47., 19., 21., 14.], [-999., -999., -999., -999., -999., -999., -999., -999., -999., -999., -999., -999.], [ 20., 20., 16., 6., 14., 18., 48., 41., 12., 27., 25., 16.], [ 22., -999., 10., 9., 8., 48., 38., 34., 24., 13., 19., 26.], [ 21., 35., 9., 14., 23., 41., 66., 67., 55., 44., 8., 32.], [ 5., 14., 14., 23., -999., 40., 14., 47., 27., 26., 28., -999.], [ 8., 13., 13., 24., 12., 58., 13., 71., 40., 23., 5., 26.]])
Теперь мы можем наши данные отобразить:
plot(data[0,:])
[<matplotlib.lines.Line2D at 0xc5f6ccc>]
Но что произойдёт если мы выберем год с отсутствующими значениями?:
plot(data[1,:])
[<matplotlib.lines.Line2D at 0xcc342ac>]
Пичалька... Нам нужно дать понять программе строящей график, что значения -999 пропущенные. Для этого можно создать так называемый masked array, который будет правильно интерпретироваться как matplotlib, который строит графики, так и numpy при расчётах, например, средних значений.
data_miss = numpy.ma.masked_equal(data, -999)
Теперь график выглядит правильно:
plot(data_miss[1,:])
[<matplotlib.lines.Line2D at 0xcd45fcc>]
data_miss[1,:].mean()
28.100000000000001
Если данные необходимо будет анализировать, то лучше открывать их сразу при помощи pandas. Примеры по работе с этим модулем с гидрометеорологическим уклоном можно найти на ещё пока не открытом секретном сайте Earthpy.org :)
Импортируем модуль:
import pandas
pandas.__version__
'0.10.1'
Теперь мы можем открыть весь файл для работы при помощи одной, хоть и оооочень длинной команды. Здесь
pd = pandas.read_fwf('sample.txt',colspecs=[(6,10),(12,16), (18,22), (24,28),
(30,34), (36,40), (42,46), (48,52),
(54,58), (60, 64), (66,70), (72, 76),
(78,82) ], names=['Year', 'JAN', 'FEB',
'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG',
'SEP', 'OCT', 'NOV', 'DEC'],
parse_dates=['Year'], index_col=['Year'] )
В итоге получаем красивую табличку, с пробелами переведёнными в NaN автоматом (не всегда так везёт :)) и возможностью легко проводить разнообразный анализ данных
pd
JAN | FEB | MAR | APR | MAY | JUN | JUL | AUG | SEP | OCT | NOV | DEC | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
Year | ||||||||||||
1967-01-01 | 22 | 35 | 28 | 39 | 22 | 23 | 59 | 46 | 49 | 45 | 36 | 29 |
1968-01-01 | 17 | 21 | 28 | 17 | 14 | 25 | 47 | 68 | NaN | NaN | 22 | 22 |
1969-01-01 | 20 | 16 | 15 | 8 | 9 | 17 | 60 | 33 | 27 | 11 | 27 | 30 |
1970-01-01 | 11 | 17 | 24 | 10 | 19 | 14 | 56 | 10 | 24 | 25 | 36 | 43 |
1971-01-01 | 91 | 39 | 17 | 4 | 12 | 13 | 31 | 67 | 78 | 16 | 31 | 26 |
1972-01-01 | 32 | 47 | 11 | 28 | 19 | 23 | 51 | 43 | 34 | 35 | 4 | 69 |
1973-01-01 | 25 | 36 | 25 | 9 | 38 | 50 | 21 | 75 | 43 | 36 | 16 | 15 |
1974-01-01 | 26 | 40 | 30 | 21 | 44 | 31 | 44 | 22 | 61 | 32 | NaN | 27 |
1975-01-01 | 49 | 16 | 15 | 29 | 12 | 28 | 35 | 25 | 35 | 43 | 24 | 54 |
1976-01-01 | 24 | 34 | 17 | 36 | 19 | 53 | 40 | 63 | 47 | 19 | 21 | 14 |
1977-01-01 | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1978-01-01 | 20 | 20 | 16 | 6 | 14 | 18 | 48 | 41 | 12 | 27 | 25 | 16 |
1979-01-01 | 22 | NaN | 10 | 9 | 8 | 48 | 38 | 34 | 24 | 13 | 19 | 26 |
1980-01-01 | 21 | 35 | 9 | 14 | 23 | 41 | 66 | 67 | 55 | 44 | 8 | 32 |
1981-01-01 | 5 | 14 | 14 | 23 | NaN | 40 | 14 | 47 | 27 | 26 | 28 | NaN |
1982-01-01 | 8 | 13 | 13 | 24 | 12 | 58 | 13 | 71 | 40 | 23 | 5 | 26 |
Можем, например, нарисовать ход значения для января:
pd.JAN.plot()
<matplotlib.axes.AxesSubplot at 0xcd562cc>
Или выбрать несколько интересующих нас лет, и отобразить данные в виде бар графика:
pd.ix['1980':'1982'].plot(kind='barh')
<matplotlib.axes.AxesSubplot at 0xbcba76c>
Или выбрать конкретный год:
pd.ix[1].plot()
<matplotlib.axes.AxesSubplot at 0xce9460c>