Составные типы данных
Содержание
Составные типы данных¶
При обработке массивов чисел есть несколько подходов.
Рассмотрим несколько типов составных переменных и взаимосвязи между ними. В качестве данных будем использовать текстовый файл с данными по времени реакции (reaction time, RT).
u='d/rt.tsv'
print(open(u).read(93)+' ...')
t v
9.585 0.429
16.368 0.335
23.729 0.357
30.93 0.373
40.303 0.439
46.804 0.402
55.448 0.382
...
1. Список¶
Входит в базовый комплект Python
и не требует импорта библиотек.
Достоинство - универсальность, недостатки - требует перебора входящих элементов, ибо никак не предполагает их единообразия: в одном списке могут быть и числа, и строки, и сложные объекты.
#создаем пустой список
vv=[]
#открываем файл
with open(u) as f:
#пропускаем строку заголовков
print('Строка заголовков: ' + f.readline())
# из каждой строчки извлекаем число между 1-м и 2-м пробелами или табуляциями (2-ю колонку)
for l in f.readlines():
# print(v) # выводит на экран каждую строку, если разкомментировать
v = l.split()[1]
vv.append(float(v))
vv
Строка заголовков: t v
[0.429,
0.335,
0.357,
0.373,
0.439,
0.402,
0.382,
0.325,
0.4,
0.303,
0.349,
nan,
0.285,
0.288,
0.308,
0.282,
0.382,
0.338,
0.307,
0.514,
0.287,
0.316,
0.29,
0.434,
nan,
nan,
0.622,
0.402,
0.393,
0.471,
0.411,
0.431,
0.519,
0.37,
0.412,
0.385,
0.513,
0.389,
0.439,
0.443,
0.698,
0.473,
0.377,
0.372,
0.357,
0.378,
0.391,
0.396]
У списка есть только длина, но в его состав могут входить другие списки.
len(vv)
48
Сгруппируем список из 6 списочков по 8 значений в каждом.
vv6x8 = []
i = 0 #текущая позиция
for igroup in range(6):
v1 = vv[i:i+8]
vv6x8.append(v1)
i += 8 #сдвигаемся к началу следующей группы
vv6x8
[[0.429, 0.335, 0.357, 0.373, 0.439, 0.402, 0.382, 0.325],
[0.4, 0.303, 0.349, nan, 0.285, 0.288, 0.308, 0.282],
[0.382, 0.338, 0.307, 0.514, 0.287, 0.316, 0.29, 0.434],
[nan, nan, 0.622, 0.402, 0.393, 0.471, 0.411, 0.431],
[0.519, 0.37, 0.412, 0.385, 0.513, 0.389, 0.439, 0.443],
[0.698, 0.473, 0.377, 0.372, 0.357, 0.378, 0.391, 0.396]]
Необходимо правильно рассчитывать размеры, и проверять крайние группы, чтобы при указании индексов не выйти за пределы массива.
Из функций обработки в чистом Python
есть только сумма. Вот так можно посчитать средние значения для каждой группы.
[sum(gr)/len(gr) for gr in vv6x8]
[0.38025000000000003, nan, 0.3585, nan, 0.43375, 0.43025]
2. Numpy Array¶
Массивы чисел, с которыми можно обращаться как с единым целым. Библиотека numpy
включает функции линейной алгебры для работы с матрицами.
Типичный импорт в виде np
.
import numpy as np
np.set_printoptions(suppress=True) #не использовать научную нотацию
В numpy
есть функция загрузки таблиц с числами loadtxt
. Поскольку в нашем файле есть заголовки (строки, а не числа) и колонки с нечисловыми значениями, то указываем дополнительные параметры: пропустить 1 ряд, использовать 2-ю колонку.
x = np.loadtxt(u, skiprows=1, usecols=(1,))
x
array([0.429, 0.335, 0.357, 0.373, 0.439, 0.402, 0.382, 0.325, 0.4 ,
0.303, 0.349, nan, 0.285, 0.288, 0.308, 0.282, 0.382, 0.338,
0.307, 0.514, 0.287, 0.316, 0.29 , 0.434, nan, nan, 0.622,
0.402, 0.393, 0.471, 0.411, 0.431, 0.519, 0.37 , 0.412, 0.385,
0.513, 0.389, 0.439, 0.443, 0.698, 0.473, 0.377, 0.372, 0.357,
0.378, 0.391, 0.396])
x.shape
(48,)
Массивы чисел в numpy
могут иметь много измерений. Чтобы изменить форму массива, достаточно указать новые размеры по нужным измерениям.
X = x.reshape(-1,8)
X
array([[0.429, 0.335, 0.357, 0.373, 0.439, 0.402, 0.382, 0.325],
[0.4 , 0.303, 0.349, nan, 0.285, 0.288, 0.308, 0.282],
[0.382, 0.338, 0.307, 0.514, 0.287, 0.316, 0.29 , 0.434],
[ nan, nan, 0.622, 0.402, 0.393, 0.471, 0.411, 0.431],
[0.519, 0.37 , 0.412, 0.385, 0.513, 0.389, 0.439, 0.443],
[0.698, 0.473, 0.377, 0.372, 0.357, 0.378, 0.391, 0.396]])
X.shape
(6, 8)
type(X)
numpy.ndarray
Мы получили шесть групп значений, но в двух группах есть пропуски, помеченные nan
. Чтобы пустые значения не мешали в вычислениях, можно сделать маску, которая исключит помеченные значения из вычислений.
Xmask = np.ma.masked_array(X, np.isnan(X))
type(Xmask)
numpy.ma.core.MaskedArray
С массивом можно обращаться к с единым целым. При этом можно применять разные операции к колонкам и строчкам. В случае необходимости менять форму и переворачивать (транспонировать).
Массивы numpy
включают методы для базовой статистики.
X.mean()
nan
X.mean(axis=1)
array([0.38025, nan, 0.3585 , nan, 0.43375, 0.43025])
Xmask.mean(axis=1)
masked_array(data=[0.38025, 0.3164285714285714, 0.3585, 0.455,
0.43374999999999997, 0.43025],
mask=[False, False, False, False, False, False],
fill_value=1e+20)
Xmask.mean(axis=1).round(2)
masked_array(data=[0.38, 0.32, 0.36, 0.46, 0.43, 0.43],
mask=[False, False, False, False, False, False],
fill_value=1e+20)
Наличие пустых значений - типичная ситуация при анализе данных, поэтому созданы специальные функции для работы с nan
.
np.nanmean(X, axis=1).round(2)
array([0.38, 0.32, 0.36, 0.46, 0.43, 0.43])
np.nanmean(X.T, axis=0).round(2)
array([0.38, 0.32, 0.36, 0.46, 0.43, 0.43])
Свойство .T
(T большое) - транспонирование.
Очень удобно выполнять действия с несколькими массивами. При этом автоматически подбираются соответствия между количеством значений в каждом (Рис. 6).
Например, нормализуем массив - вычтем среднее и разделим на стандартное отклонение каждую строчку.
Xnorm = np.array((Xmask.T - Xmask.T.mean(axis=0))/Xmask.T.std(axis=0)).T.round(3)
Xnorm
array([[ 1.259, -1.168, -0.6 , -0.187, 1.517, 0.562, 0.045, -1.427],
[ 2.082, -0.335, 0.811, nan, -0.783, -0.708, -0.21 , -0.858],
[ 0.313, -0.273, -0.686, 2.071, -0.952, -0.566, -0.912, 1.005],
[ nan, nan, 2.118, -0.672, -0.786, 0.203, -0.558, -0.304],
[ 1.605, -1.2 , -0.409, -0.918, 1.492, -0.842, 0.099, 0.174],
[ 2.517, 0.402, -0.501, -0.547, -0.688, -0.491, -0.369, -0.322]])
Массив можно редуцировать до списка.
Xnorm.tolist()
[[1.259, -1.168, -0.6, -0.187, 1.517, 0.562, 0.045, -1.427],
[2.082, -0.335, 0.811, nan, -0.783, -0.708, -0.21, -0.858],
[0.313, -0.273, -0.686, 2.071, -0.952, -0.566, -0.912, 1.005],
[nan, nan, 2.118, -0.672, -0.786, 0.203, -0.558, -0.304],
[1.605, -1.2, -0.409, -0.918, 1.492, -0.842, 0.099, 0.174],
[2.517, 0.402, -0.501, -0.547, -0.688, -0.491, -0.369, -0.322]]
type(_)
list
3. Pandas DataFrame¶
Для работы со структурированными данными библиотека pandas
предлагает структуры:
1-мерная Series
2-мерная DataFrame
Рекомендуемый способ представления трех- и более мерных данных - использовать таблицы с многоуровневым индексом (MultiIndex).
Типичный импорт в виде pd
. Библиотека включает массу функций для удобного обращения с файлами, датами, статистикой и рисунками.
import pandas as pd
D = pd.read_table(u)
D
t | v | |
---|---|---|
0 | 9.585 | 0.429 |
1 | 16.368 | 0.335 |
2 | 23.729 | 0.357 |
3 | 30.930 | 0.373 |
4 | 40.303 | 0.439 |
5 | 46.804 | 0.402 |
6 | 55.448 | 0.382 |
7 | 62.401 | 0.325 |
8 | 68.910 | 0.400 |
9 | 73.463 | 0.303 |
10 | 77.697 | 0.349 |
11 | 81.661 | NaN |
12 | 86.385 | 0.285 |
13 | 90.470 | 0.288 |
14 | 95.114 | 0.308 |
15 | 99.548 | 0.282 |
16 | 104.056 | 0.382 |
17 | 105.844 | 0.338 |
18 | 107.955 | 0.307 |
19 | 109.636 | 0.514 |
20 | 111.487 | 0.287 |
21 | 113.394 | 0.316 |
22 | 115.732 | 0.290 |
23 | 117.612 | 0.434 |
24 | 128.119 | NaN |
25 | 135.000 | NaN |
26 | 143.557 | 0.622 |
27 | 151.932 | 0.402 |
28 | 160.933 | 0.393 |
29 | 168.975 | 0.471 |
30 | 177.819 | 0.411 |
31 | 184.975 | 0.431 |
32 | 191.480 | 0.519 |
33 | 195.884 | 0.370 |
34 | 199.594 | 0.412 |
35 | 203.637 | 0.385 |
36 | 207.373 | 0.513 |
37 | 211.929 | 0.389 |
38 | 216.615 | 0.439 |
39 | 219.947 | 0.443 |
40 | 224.455 | 0.698 |
41 | 226.741 | 0.473 |
42 | 228.837 | 0.377 |
43 | 230.642 | 0.372 |
44 | 232.313 | 0.357 |
45 | 234.108 | 0.378 |
46 | 235.799 | 0.391 |
47 | 238.050 | 0.396 |
При чтении таблицы создаются индексы - для наблюдений (строчек) и для переменных (колонок).
D.columns
Index(['t', 'v'], dtype='object')
D.index
RangeIndex(start=0, stop=48, step=1)
Можно задать в качестве индекса одну из колонок с данными. По-умолчанию используется автоматическая нумерация (как в этом примере).
Каждая строчка или колонка DataFrame
- это серия с соответствующим индексом. К значениям можно обращаться по их индексу в любом порядке. Например, чтобы получить третье по счету значение ВР:
D['v'][2] # вначале колонка, потом позиция в колонке
0.35700000000000004
D.loc[2]['v'] # вначале строка, потом колонка
0.35700000000000004
D.loc[2,'v'] # обе координаты строка и колонка
0.35700000000000004
Способ с одновременным указанием строки и колонки является предпочтительным, потому что в одно действие происходит обращение к нужной ячейке таблицы. Именно этот способ используют для замены конкретного значения, например
D.loc[2,'v'] = D['v'].mean()
Можно использовать порядковый индекс позиции, как в массивах numpy.
D.iloc[2,1]
0.35700000000000004
Таблицы включают мощные средства манипуляции данными.
При необходимости разбиения массива данных на группы признаки принадлежности к разным группам добавляют в виде новых колонок, а уже информацию в этих колонках используют в разных сочетаниях при отборе подвыборок и сравнительном анализе.
Добавим в качестве группового признака номер группы для разделения всей последовательности на 6 одинаковых групп, идущих друг за другом.
ngroup = 6
D['g'] = np.kron(range(1,ngroup+1), np.ones(int(len(D)/ngroup), int))
Например, чтобы посчитать среднее ВР для разных МСИ (в колонке 'g'
), можно сделать так:
D.groupby('g')['v'].mean()
g
1 0.380250
2 0.316429
3 0.358500
4 0.455000
5 0.433750
6 0.430250
Name: v, dtype: float64
Таблицы и Серии подходят для использования в операциях линейной алгебры из numpy
. Если надо, значения таблицы можно извлечь в виде массива или в виде списка.
D.values
array([[ 9.585, 0.429, 1. ],
[ 16.368, 0.335, 1. ],
[ 23.729, 0.357, 1. ],
[ 30.93 , 0.373, 1. ],
[ 40.303, 0.439, 1. ],
[ 46.804, 0.402, 1. ],
[ 55.448, 0.382, 1. ],
[ 62.401, 0.325, 1. ],
[ 68.91 , 0.4 , 2. ],
[ 73.463, 0.303, 2. ],
[ 77.697, 0.349, 2. ],
[ 81.661, nan, 2. ],
[ 86.385, 0.285, 2. ],
[ 90.47 , 0.288, 2. ],
[ 95.114, 0.308, 2. ],
[ 99.548, 0.282, 2. ],
[104.056, 0.382, 3. ],
[105.844, 0.338, 3. ],
[107.955, 0.307, 3. ],
[109.636, 0.514, 3. ],
[111.487, 0.287, 3. ],
[113.394, 0.316, 3. ],
[115.732, 0.29 , 3. ],
[117.612, 0.434, 3. ],
[128.119, nan, 4. ],
[135. , nan, 4. ],
[143.557, 0.622, 4. ],
[151.932, 0.402, 4. ],
[160.933, 0.393, 4. ],
[168.975, 0.471, 4. ],
[177.819, 0.411, 4. ],
[184.975, 0.431, 4. ],
[191.48 , 0.519, 5. ],
[195.884, 0.37 , 5. ],
[199.594, 0.412, 5. ],
[203.637, 0.385, 5. ],
[207.373, 0.513, 5. ],
[211.929, 0.389, 5. ],
[216.615, 0.439, 5. ],
[219.947, 0.443, 5. ],
[224.455, 0.698, 6. ],
[226.741, 0.473, 6. ],
[228.837, 0.377, 6. ],
[230.642, 0.372, 6. ],
[232.313, 0.357, 6. ],
[234.108, 0.378, 6. ],
[235.799, 0.391, 6. ],
[238.05 , 0.396, 6. ]])
И, наоборот, из массива можно сделать датафрейм (таблицу). Если не указать индексы - они создадутся автоматически.
pd.DataFrame(X)
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
---|---|---|---|---|---|---|---|---|
0 | 0.429 | 0.335 | 0.357 | 0.373 | 0.439 | 0.402 | 0.382 | 0.325 |
1 | 0.400 | 0.303 | 0.349 | NaN | 0.285 | 0.288 | 0.308 | 0.282 |
2 | 0.382 | 0.338 | 0.307 | 0.514 | 0.287 | 0.316 | 0.290 | 0.434 |
3 | NaN | NaN | 0.622 | 0.402 | 0.393 | 0.471 | 0.411 | 0.431 |
4 | 0.519 | 0.370 | 0.412 | 0.385 | 0.513 | 0.389 | 0.439 | 0.443 |
5 | 0.698 | 0.473 | 0.377 | 0.372 | 0.357 | 0.378 | 0.391 | 0.396 |
Посмотрим типы переменных, созданных разными способами.
type(vv), type(X), type(D['g']), type(D)
(list, numpy.ndarray, pandas.core.series.Series, pandas.core.frame.DataFrame)