Я работаю над корреляционным исследованием на Python, для которого требуется матрица расстояний между каждой парой координат в наборе данных с 60 000 точками данных. Я попытался векторизовать и использовать геопанды, но проблема с геопандами заключается в том, что для запуска функции расстояния мне нужны данные x и y в повторяющихся списках (данные x повторяют набор из 60 000 башен, а данные y повторяют каждую координату 60 000 раз). подряд), делая каждый список длиной 3,6e9 значений, и на моем компьютере заканчивается память до того, как это будет завершено, или когда я пытаюсь запустить его на удаленном рабочем столе в моей школе, это занимает больше получаса, и у меня нет Мне не удалось его успешно запустить. Вот код, который я запускаю:
#Florida Tower Matrix
#take coordinates of Florida towers
#CHECK THE LAT/LONG order
import geojson
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point
features = []
with open("/Users/katcn/Desktop/Spring 2024/Research/PleaseWork.geojson") as f:
gj = geojson.load(f)
for i in range(59629):
features.append(gj['features'][i]["geometry"]['coordinates'])
#OR make the X matrix all in one column
#make the Y matrix repeat each value 59000 times
longitude = []
latitude = []
for i in range(len(features)):
for j in range(len(features)):
longitude.append(features[j][0])
for k in range(len(features)):
latitude.append(features[i][0])
dict = {"longitude" : longitude, "latitude" : latitude}
df = pd.DataFrame(dict)
dict2 = {"longitude" : longitude, "latitude" : latitude}
df2 = pd.DataFrame(dict2)
#calculate distance between two towers
geometry = [Point(xy) for xy in zip(df.longitude, df.latitude)]
gdf = gpd.GeoDataFrame(df, crs = {'init': 'epsg:4326'}, geometry=geometry)
geometry2 = [Point(xy) for xy in zip(df2.longitude, df2.latitude)]
gdf2 = gpd.GeoDataFrame(df2, crs = {'init': 'epsg:4326'}, geometry=geometry2)
distances = gdf.geometry.distance(gdf2.geometry)
print(distances)
Любые предложения о том, как по-другому подойти к этой проблеме, чтобы сделать ее более разумной во время выполнения, были бы великолепны.
Для этого вам на самом деле не нужно использовать функцию расстояния geopandas
. По сути, единственное, что вам нужно, это scipy
. Начните с помещения ваших координат в массив:
import numpy as np
import pandas as pd
import geojson
from scipy.spatial import distance
with open("/Users/katcn/Desktop/Spring 2024/Research/PleaseWork.geojson") as f:
gj = geojson.load(f)
coords = np.array([feature["geometry"]["coordinates"] for feature in gj['features']])
dist_matrix = distance.cdist(coords, coords, 'euclidean')
dist_df = pd.DataFrame(dist_matrix)
print(dist_df)
Поскольку вы не предоставили данные, я создал образец набора данных с точками континентальной части США (здесь 60 000 точек от KKey West, Флорида до канадской границы и от Западного побережья до Восточного побережья):
import numpy as np
import pandas as pd
from scipy.spatial import distance
import time
np.random.seed(42)
latitudes = np.random.uniform(low=25.0, high=49.0, size=60000)
longitudes = np.random.uniform(low=-125.0, high=-66.0, size=60000)
coords = np.column_stack((latitudes, longitudes))
start_time = time.time()
dist_matrix = distance.cdist(coords, coords, 'euclidean')
end_time = time.time()
elapsed_time = end_time - start_time
print("Distance matrix computed in {:.2f} seconds".format(elapsed_time))
print("Shape of the distance matrix:", dist_matrix.shape)
Какие взять
Distance matrix computed in 185.64 seconds
Shape of the distance matrix: (60000, 60000)
Итак, примерно 3 минуты.
ОБНОВЛЕНИЕ: ИСПОЛЬЗОВАНИЕ ГЕОДЕЗИЧЕСКОГО РАССТОЯНИЯ
Это работает, но требует времени:
import numpy as np
import pandas as pd
from geopy.distance import geodesic
import time
np.random.seed(42)
latitudes = np.random.uniform(low=25.0, high=49.0, size=60000)
longitudes = np.random.uniform(low=-125.0, high=-66.0, size=60000)
coords = list(zip(latitudes, longitudes))
def geodesic_distance_matrix(coords):
n = len(coords)
dist_matrix = np.zeros((n, n))
for i in range(n):
for j in range(i+1, n):
dist_matrix[i, j] = geodesic(coords[i], coords[j]).meters
dist_matrix[j, i] = dist_matrix[i, j]
return dist_matrix
start_time = time.time()
dist_matrix = geodesic_distance_matrix(coords)
end_time = time.time()
elapsed_time = end_time - start_time
print("Distance matrix computed in {:.2f} seconds".format(elapsed_time))
print("Shape of the distance matrix:", dist_matrix.shape)
Другой подход — использовать haversine
следующим образом:
import numpy as np
import pandas as pd
import time
def haversine(lat1, lon1, lat2, lon2):
lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])
dlon = lon2 - lon1
dlat = lat2 - lat1
a = np.sin(dlat/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2
c = 2 * np.arcsin(np.sqrt(a))
r = 6371
return c * r
np.random.seed(42)
latitudes = np.random.uniform(low=25.0, high=49.0, size=60000)
longitudes = np.random.uniform(low=-125.0, high=-66.0, size=60000)
coords = np.column_stack((latitudes, longitudes))
start_time = time.time()
n = coords.shape[0]
dist_matrix = np.zeros((n, n))
for i in range(n):
dist_matrix[i, :] = haversine(coords[i, 0], coords[i, 1], coords[:, 0], coords[:, 1])
end_time = time.time()
elapsed_time = end_time - start_time
print("Distance matrix computed in {:.2f} seconds".format(elapsed_time))
print("Shape of the distance matrix:", dist_matrix.shape)
который возвращает
Distance matrix computed in 463.75 seconds
Shape of the distance matrix: (60000, 60000)
то есть более чем в два раза больше времени евклидова расстояния.
array([[ 0. , 1683.78776508, 1717.22879531, ..., 1706.77761313,
3857.52001651, 1579.36733204],
[1683.78776508, 0. , 2008.58039812, ..., 2813.5398641 ,
4379.29512353, 2340.45973263],
[1717.22879531, 2008.58039812, 0. , ..., 1151.61134582,
2399.40193517, 560.14519709],
...,
[1706.77761313, 2813.5398641 , 1151.61134582, ..., 0. ,
2244.42005933, 593.632848 ],
[3857.52001651, 4379.29512353, 2399.40193517, ..., 2244.42005933,
0. , 2291.13872501],
[1579.36733204, 2340.45973263, 560.14519709, ..., 593.632848 ,
2291.13872501, 0. ]])
здесь размерность составляет километры. Если вам нужны мили, вам придется поменять
r = 6371
к
r = 3956
Вы имеете в виду геодезическое расстояние?
Это вполне возможно, но сама природа геодезического расстояния означает, что при наличии 60 000 точек вам придется сделать 3,6 миллиарда сравнений. Позвольте мне рассмотреть другой способ.
Да, я имею в виду геодезическое расстояние, извините, поскольку координаты относятся к опорам ЛЭП во Флориде, я думаю, что геодезические будут лучше, чем евклидовы.
@KatNeumann Проверьте обновления.
Если это то, на что вы надеялись, не забудьте пометить ответ как принятый, чтобы он был удален из списка вопросов без ответов.
Извините за задержку, у меня он запускается через 10 минут, и это здорово, но у меня были проблемы с попыткой перенести данные в CSV, потому что это занимает слишком много времени, я запущу его еще раз, и он покажет мне переменные, прежде чем я попробуйте сохранить, чтобы я мог отметить это как ответ
@KatNeumann .csv
не лучший тип файла для такого объема данных, попробуйте .parquet
или просто numpy .npz
@dankal444 Хорошо, я провел последние два дня, пробуя паркет, npz и коврик, потому что мне нужно, чтобы это можно было импортировать в Matlab, но любой формат файла, который я использую, не работает. Я использую код VS, тестирую функции записи в файл и могу записать небольшие матрицы, но матрица расстояний ломается, ничего не записывая в файлы. Я не уверен, почему это происходит, поскольку мой отладчик не выдает ошибку, запуск файла просто прекращается. Единственное, что мне удалось написать, это в CSV, но на прогон трети матрицы ушло около 5 часов. Знаете ли вы какие-либо способы ускорить запись в CSV?
@KatNeumann Может быть, некоторые из ваших проблем были связаны с размером файла/ОЗУ, и все, что вам нужно, это разделить сохраненные данные на части? polars
пакет работает быстро, когда дело доходит до чтения/записи CSV, стоит попробовать. Matlab должен читать файлы паркета, вы платите за это программное обеспечение, чтобы вы могли связаться с авторами, почему оно не работает. Я думаю, файл слишком большой
@KatNeumann Какие данные вы пытаетесь передать в CSV? Какие преобразования вы делаете с матрицей расстояний, прежде чем попытаться отправить ее в CSV?
@KatNeumann Я предложил альтернативный ответ на ваш вопрос. Если это решит всю вашу проблему, отметьте его как принятый ответ, чтобы вопрос больше не оставался без ответа.
Этот ответ предназначен для решения следующей проблемы, а именно сохранения полученных результатов в CSV-файл. Учитывая объем данных, pd.to_csv()
— не лучший вариант.
Здесь я опираюсь на предыдущее решение, вычисляя матрицу расстояний. Обратите внимание, что матрица на самом деле разрежена, поскольку расстояние между x
и y
такое же, как расстояние между y
и x
(поэтому матрица треугольная.
Я создаю фрейм данных, добавляя точки (lat
, long
). Затем, используя polar
, я преобразую фрейм данных pandas в полярный фрейм данных и, наконец, сохраняю его:
import numpy as np
import pandas as pd
import time
def haversine(lat1, lon1, lat2, lon2):
lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])
dlon = lon2 - lon1
dlat = lat2 - lat1
a = np.sin(dlat/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2)**2
c = 2 * np.arcsin(np.sqrt(a))
r = 6371
return c * r
np.random.seed(42)
latitudes = np.random.uniform(low=25.0, high=49.0, size=10000)
longitudes = np.random.uniform(low=-125.0, high=-66.0, size=10000)
coords = np.column_stack((latitudes, longitudes))
start_time = time.time()
n = coords.shape[0]
dist_matrix = np.zeros((n, n))
for i in range(n):
dist_matrix[i, :] = haversine(coords[i, 0], coords[i, 1], coords[:, 0], coords[:, 1])
end_time = time.time()
elapsed_time = end_time - start_time
print("Distance matrix computed in {:.2f} seconds".format(elapsed_time))
print("Shape of the distance matrix:", dist_matrix.shape)
point_labels = [f"Point {i}" for i in range(n)]
dist_df = pd.DataFrame(dist_matrix, index=point_labels, columns=point_labels)
distance = dist_df.loc['Point 0', 'Point 1']
print(f"Distance from Point 0 to Point 1: {distance} km")
lat_lon_index = pd.MultiIndex.from_arrays([latitudes, longitudes], names=['Latitude', 'Longitude'])
dist_df.index = lat_lon_index
dist_df.columns = lat_lon_index
lat_from, lon_from = latitudes[0], longitudes[0]
lat_to, lon_to = latitudes[1], longitudes[1]
distance = dist_df.loc[(lat_from, lon_from), (lat_to, lon_to)]
print(f"Distance from ({lat_from}, {lon_from}) to ({lat_to}, {lon_to}): {distance} km")
Теперь dist_df
выглядит так
Чтобы сохранить его в CSV, просто сделайте следующее:
dist_df_reset = dist_df.reset_index()
polars_df = pl.from_pandas(dist_df_reset)
polars_df.write_csv('polars_distance_matrix.csv')
Файл будет:
ВАЖНОЕ ПРИМЕЧАНИЕ: Это будет намного быстрее, но вы не можете надеяться, что это займет очень мало времени с таким количеством точек.
Хорошо, я попробовал это на своем рабочем столе Windows, и у меня проблемы с памятью, так как я всего лишь пользователь, а выделение недостаточно велико для переиндексации кадра данных (хотя я проверил на нем около 33 ГБ. Я избавился от переиндексации, чтобы посмотреть, сохранится ли CSV, и у него закончилась память malloc, которая, как я полагаю, является памятью модуля Polars. Я попытался затем запустить его на своем ПК, но возникла проблема с Mac OS, работающей на Python под зависимостями Rosetta. для пакета Polars, который я могу попытаться исправить.
Вы можете сохранить его как файл данных. Почему вы хотите сохранить его как CSV-файл?
Мне просто нужно это в любом формате файла, который я могу импортировать в MATLAB. Но любой формат файла, кроме CSV, автоматически прекращает работу, ничего не выполняя. Я попытался просто запустить файл Python через MATLAB, чтобы экспорт не потребовался, но мне не удалось заставить MATLAB запустить numpy, который используется для создания матрицы расстояний.
Это прекрасно работает и у меня работает примерно за минуту. Мне интересно, поскольку мне нужны выходные данные в метрах, а координаты - широта/длина, следует ли мне использовать различные расчеты расстояний, которые предлагает Scipy, для получения более точного результата, чем евклидово? Знаете ли вы, каковы единицы евклидова результата?