Заменить выбросы сгруппированных столбцов на среднее значение группы на основе определенного zscore

#python #dataframe #data-science

#python #фрейм данных #наука о данных

Вопрос:

У меня очень огромный фрейм данных со многими точками данных на карте с выбросами, которые очень близки друг к другу в наборе данных (широты и долготы). Я хотел бы сгруппировать все строки, как показано ниже, для столбца A, вычислить их zscores и заменить каждое значение в группе, у которой zscore> 1.5, на среднее значение для группы.

 df =

[data][1]
 

Я безуспешно пробовал таблицу значений zscore

 <**zscore = lambda x : (x - x.mean()) / x.std()
grouped_df = df.groupby("A")
transformed_df = grouped_df.transform(zscore)
transformed_df which gives me a table with zscores**>
 

Комментарии:

1. Привет, неясно, в каком столбце (или столбцах) вы хотите вычислить zscore : это на расстояниях между точками с одинаковыми метками «A»? Это включено lat и lon независимо?

2. ДА. Это на расстояниях между точками с одинаковыми метками на широте и длине после группировки значений столбца A [Like] df.groupby(«A»), но вычисления zscore выполняются для широты и длины

Ответ №1:

Вы можете использовать haversine_distances from scikit-learn для вычисления расстояний между точкой и центром тяжести точки в той же группе. Учитывая, что у вас должны быть очень близкие точки, вы можете аппроксимировать широту и долготу центроида средним значением широты и долготы точек в группе.

Вот пример, основанный на данных из городов Великобритании (это бесплатный образец, который вы можете скачать здесь). В частности, данные содержат для каждого города его координаты и округ (которые вы можете рассматривать как группу в своих настройках):

                           name          county  latitude  longitude
0                 Aaron's Hill          Surrey  51.18291   -0.63098
1                  Abbas Combe        Somerset  51.00283   -2.41825
2                     Abberley  Worcestershire  52.30522   -2.37574
3                     Abberton           Essex  51.83440    0.91066
4                     Abberton  Worcestershire  52.17955   -2.00817
5                    Abberwick  Northumberland  55.41325   -1.79720
6                   Abbess End           Essex  51.78000    0.28172
7                Abbess Roding           Essex  51.77815    0.27685
8                        Abbey           Devon  50.88896   -3.22276
9  Abbeycwmhir / Abaty Cwm-hir           Powys  52.33104   -3.38988
 

И вот код, который нужно изменить, чтобы решить вашу проблему:

 from math import radians

import numpy as np
import pandas as pd
from sklearn.metrics.pairwise import haversine_distances

df = pd.read_csv('uk-towns-sample.csv', usecols=['name', 'county', 'latitude', 'longitude'])

# Compute coordinates of the centroid for each county (group)
dist_county = pd.DataFrame(df.groupby('county').agg({'latitude': np.mean, 'longitude': np.mean}))

# Convert latitude and longitude to radians (it is needed by the function to compute haversine distance)
df[['latitude_radians', 'longitude_radians']] = df[['latitude', 'longitude']].applymap(radians)
dist_county[['latitude_radians', 'longitude_radians']] = dist_county[['latitude', 'longitude']].applymap(radians)

# Compute the distance of each town w.r.t. the centroid of its conunty
df['dist'] = df[['county', 'latitude_radians', 'longitude_radians']].apply(
    lambda x: haversine_distances(
        [x[['latitude_radians', 'longitude_radians']].values],
        [dist_county.loc[x['county']][['latitude_radians', 'longitude_radians']].values]
    )[0][0] * 6371000/1000,  # multiply by Earth radius to get kilometers,
    axis=1
)

# Compute mean and std of distances by county
county_stats = df.groupby('county').agg({'dist': [np.mean, np.std]})

# Compute the z-score using the distance of each town w.r.t. the centroid of its county, and the mean and std of distances for that county
df['zscore'] = df.apply(
    lambda x: (x['dist'] - county_stats.loc[x['county']][('dist', 'mean')] ) / county_stats.loc[x['county']][('dist', 'std')],
    axis=1
)

# Change latitude and longitude of the outliers with those of the centroid of their counties
df.loc[df.zscore > 1.5, ['latitude', 'longitude']] = df[df.zscore > 1.5].merge(
    dist_county, left_on='county', right_on=dist_county.index, how='left'
)[['latitude_y', 'longitude_y']].values
 

Результирующий df фрейм данных выглядит следующим образом:

               name           county  latitude  longitude  latitude_radians  longitude_radians       dist    zscore
0     Aaron's Hill           Surrey  51.18291   -0.63098          0.893310          -0.011013  12.479147 -0.293419
1      Abbas Combe         Somerset  51.00283   -2.41825          0.890167          -0.042206  35.205157  1.088695
2         Abberley   Worcestershire  52.30522   -2.37574          0.912898          -0.041464  17.014249  0.266168
3         Abberton            Essex  51.83440    0.91066          0.904681           0.015894  24.504285 -0.254400
4         Abberton   Worcestershire  52.17955   -2.00817          0.910705          -0.035049  11.906150 -0.663460
...            ...              ...       ...        ...               ...                ...        ...       ...
1795         Ayton     Berwickshire  55.84232   -2.12285          0.974632          -0.037051   5.899085  0.007876
1796         Ayton    Tyne and Wear  54.89416   -1.55643          0.958084          -0.027165   3.192591 -0.935937
 

Если вы посмотрите на выбросы для округа Эссекс, новые координаты соответствуют координатам центроида, т.Е. (51.846594, 0.554532):

              name county   latitude  longitude
414   Aimes Green  Essex  51.846594   0.554532
1721       Aveley  Essex  51.846594   0.554532