2014-01-03 3 views
3

У меня снова возникла странная проблема.Использование scipy pdist в кадре данных pandas (и) списки

Предположим, у меня есть следующие фиктивного кадра данных (путем демонстрации моей проблемы):

import numpy as np 
import pandas as pd 
import string 

# Test data frame 
N = 3 
col_ids = string.letters[:N] 
df = pd.DataFrame(
    np.random.randn(5, 3*N), 
    columns=['{}_{}'.format(letter, coord) for letter in col_ids for coord in list('xyz')]) 

df 

Это производит:

 A_x   A_y   A_z   B_x   B_y   B_z   C_x   C_y   C_z 
0 -1.339040 0.185817 0.083120 0.498545 -0.569518 0.580264 0.453234 1.336992 -0.346724 
1 -0.938575 0.367866 1.084475 1.497117 0.349927 -0.726140 -0.870142 -0.371153 -0.881763 
2 -0.346819 -1.689058 -0.475032 -0.625383 -0.890025 0.929955 0.683413 0.819212 0.102625 
3 0.359540 -0.125700 -0.900680 -0.403000 2.655242 -0.607996 1.117012 -0.905600 0.671239 
4 1.624630 -1.036742 0.538341 -0.682000 0.542178 -0.001380 -1.126426 0.756532 -0.701805 

Теперь я хотел бы использовать scipy.spatial.distance.pdist на этом кадре данных панд , Это оказывается довольно нетривиальным процессом. То, что pdist - это вычисление расстояния между m точками с использованием евклидова расстояния (2-нормы) в качестве метрики расстояния между точками. Точки расположены как m n-мерные векторы строк в матрице X (source).

Итак, есть несколько вещей, которые нужно сделать для создания функции, которая работает с кадром данных pandas, так что может использоваться функция pdist. Вы заметите, что pdist удобен, когда количество точек становится очень большим. Я попытался сделать свой собственный, который работает для однострочного кадра данных, но я не могу заставить его работать, в идеале, во всем кадре данных сразу.

Вот моя попытка:.

from scipy.spatial.distance import pdist, squareform 
import numpy as np 
import pandas as pd 
import string 

def Euclidean_distance(df): 
    EcDist = pd.DataFrame(index=df.index) # results container 
    arr = df.values # Store data frame values into a numpy array 
    tag_list = [num for elem in arr for num in elem] # flatten numpy array into single list 
    tag_list_3D = zip(*[iter(tag_list)]*3) # separate list into length = 3 sub-lists, that pdist() can work with 
    EcDist = pdist(tag_list_3D) # the distance between m points using Euclidean distance (2-norm) 
    return EcDist 

Сначала я начинаю мой создания контейнера результаты в виде панд, чтобы сохранить результат в вторых, я сохранить кадр панды данных как Numpy массива, для того, чтобы получить его в виде списка на следующем шаге. Он должен быть списком, потому что функция pdist работает только с списками. При сохранении фрейма данных в массив он сохраняет его как список в списке. Это должно быть сглажено, которое сохраняется в переменной «tag_list». В-третьих, tag_list активируется в виде суб-списков длины три, так что координаты x, y и z могут быть получены для каждой точки, что может быть использовано для нахождения евклидова расстояния между всеми этими точками (в этом примере есть три точки: A, B и C, каждый из которых является трехмерным).

Как сказано, функция работает, если кадр данных является одной строкой, но при использовании функции в данном примере он вычисляет евклидово расстояние для 5x3 точек, что дает в общей сложности 105 расстояний. То, что я хочу сделать, это вычислить расстояния на строку (так что pdist должен работать только с вектором 1x3 за раз). Такое, что для этого примера будет выглядеть мои окончательные результаты что-то вроде этого:

dist_1 dist_2 dist_3 
0 0.807271 0.142495 1.759969 
1 0.180112 0.641855 0.257957 
2 0.196950 1.334812 0.638719 
3 0.145780 0.384268 0.577387 
4 0.044030 0.735428 0.549897 

(это только фиктивные номера, чтобы показать желаемую форму)

Поэтому как я могу получить свою функцию, чтобы применить к кадр данных по-разному? Или еще лучше, как я могу заставить его выполнить функцию во всем кадре данных сразу, а затем сохранить результат в новом фрейме данных?

Любая помощь будет очень признательна. Благодарю.

+0

Можете ли вы уточнить, что именно вы хотите? Вы говорите, что для каждой строки вашего DataFrame вам нужна новая строка, содержащая попарные расстояния между тремя точками в этой строке? Вы, кажется, указываете, что хотите увеличить это для большего количества очков, но если вы добавите больше очков за строку, ваш DataFrame станет довольно громоздким. Почему бы не иметь отдельные строки для каждой точки, с дополнительным столбцом, который указывает «идентификатор группы»? – BrenBarn

+3

Вы не можете получить то, что хотите от 'scipy.spatial.distance'. Я знаю, потому что я работаю над улучшением этого, что позволит делать то, что вам нужно, см. PR [здесь] (https://github.com/scipy/scipy/pull/3163). Может быть, в 0.14 ... – Jaime

+0

@BrenBarn, Хорошо, предположим, что у меня есть матричный массив. Строки массива содержат 3d координаты для точек в пространстве, подобных этому [(x, y, z), ..., (x, y, z)]. Теперь я хочу функцию, которая вычисляет евклидовы расстояния между всеми этими точками в этой строке. Предположим, что я хочу сделать то же самое во всех строках матрицы. В моем случае у меня есть 12 очков за строку, поэтому будет 66 (n (n-1)/12) ребер, если мы рассмотрим точки как полный граф. Следовательно, мой вопрос заключается в следующем: как сделать такую ​​функцию? – Astrid

ответ

4

Если я правильно понял, у вас есть «группы» точек. В вашем примере каждая группа имеет три точки, которые вы называете A, B и C. A представлена ​​тремя столбцами A_x, A_y, A_z, а также для B и C.

Что я предлагаю, так это то, что вы реструктурируете свой " широкоформатные "данные в" длинную "форму, в которой каждая строка содержит только один пункт. Каждая строка затем будет иметь только три столбца для координат, а затем вы добавите дополнительный столбец для представления, в какой группе находится точка.Вот пример:

>>> d = pandas.DataFrame(np.random.randn(12, 3), columns=["X", "Y", "Z"]) 
>>> d["Group"] = np.repeat([1, 2, 3, 4], 3) 
>>> d 
      X   Y   Z Group 
0 -0.280505 0.888417 -0.936790  1 
1 0.823741 -0.428267 1.483763  1 
2 -0.465326 0.005103 -1.107431  1 
3 -1.009077 -1.618600 -0.443975  2 
4 0.535634 0.562617 1.165269  2 
5 1.544621 -0.858873 -0.349492  2 
6 0.839795 0.720828 -0.973234  3 
7 -2.273654 0.125304 0.469443  3 
8 -0.179703 0.962098 -0.179542  3 
9 -0.390777 -0.715896 -0.897837  4 
10 -0.030338 0.746647 0.250173  4 
11 -1.886581 0.643817 -2.658379  4 

Три точки с помощью групповой == 1 соответствуют A, B и C в вашем первом ряду; три точки с группой == 2 соответствуют A, B и C во втором ряду; и т.д.

С этой структурой, вычислением попарных расстояний от группы, получавшей pdist становится простым:

>>> d.groupby('Group')[["X", "Y", "Z"]].apply(lambda g: pandas.Series(distance.pdist(g), index=["D1", "D2", "D3"])) 
      D1  D2  D3 
Group        
1  2.968517 0.918435 2.926395 
2  3.119856 2.665986 2.309370 
3  3.482747 1.314357 2.346495 
4  1.893904 2.680627 3.451939 

Можно сделать подобную вещь с существующей установкой, но это будет более неудобно. Проблема с тем, как вы его настроили, заключается в том, что вы закодировали важную информацию в сложном для извлечения способом. Информация о том, какие столбцы являются координатами X и которые представляют собой координаты Y или Z, а также информацию о том, какие столбцы относятся к точке A по сравнению с B или C, в вашей настройке, кодируется в текстовых именах . Вы, как человек, можете видеть, какие столбцы являются значениями X, просто взглянув на них, но указав, что программным образом требуется разбор строк имен столбцов.

Вы можете видеть это в том, как вы создали имена столбцов в своем бизнесе '{}_{}'.format(letter, coord). Это означает, что для использования pdist в ваших данных вам нужно будет выполнить обратную операцию разбора имен столбцов в виде строк, чтобы решить, какие столбцы сравнивать. Излишне говорить, что это будет неудобно. С другой стороны, если вы помещаете данные в «длинную» форму, таких трудностей нет: координаты X всех точек выстраиваются в один столбец, а также для Y и Z, а информация о том, какие точки для сравнения также содержится в один столбец (столбец «Группа»).

Если вы хотите делать крупномасштабные операции над подмножествами данных, обычно лучше разделить вещи на отдельные строки. Это позволяет вам использовать мощность groupby, а также, как правило, то, что ожидается от скудных инструментов.

+0

Это отлично! Я не рассматривал «укладку» всех данных, как вы это делали. Я полагаю, что я мог бы просто использовать метку времени, как вместо метки вашей группы (поскольку каждая метка времени имеет 12 3D-координат, а pdist требуется для каждой отметки времени). Он будет производить довольно большой массив, но это не большая проблема. Я дам этот выстрел и отчитаюсь. Благодаря! – Astrid

Смежные вопросы