Как рассчитать общий объем / пересечение между 2, 2D графиками kde в python? - PullRequest
0 голосов
/ 11 июля 2020

У меня есть 2 набора точек данных:

import random
import pandas as pd
A = pd.DataFrame({'x':[random.uniform(0, 1) for i in range(0,100)], 'y':[random.uniform(0, 1) for i in range(0,100)]})
B = pd.DataFrame({'x':[random.uniform(0, 1) for i in range(0,100)], 'y':[random.uniform(0, 1) for i in range(0,100)]})

Для каждого из этих наборов данных я могу создать объединенный график следующим образом:

import seaborn as sns
sns.jointplot(x=A["x"], y=A["y"], kind='kde')
sns.jointplot(x=B["x"], y=B["y"], kind='kde')

Есть ли способ вычислить " общая площадь "между этими двумя совместными участками?

Под общей площадью я имею в виду, если вы поместите один совместный участок" внутри "другого, какова общая площадь пересечения. Итак, если вы представите эти 2 совместных участка в виде гор и поместите одну гору внутри другой, насколько одна упадет внутри другой?

EDIT

Чтобы сделать мой вопрос более ясен:

import matplotlib.pyplot as plt
import scipy.stats as st

def plot_2d_kde(df):
    # Extract x and y
    x = df['x']
    y = df['y']
    # Define the borders
    deltaX = (max(x) - min(x))/10
    deltaY = (max(y) - min(y))/10
    xmin = min(x) - deltaX
    xmax = max(x) + deltaX
    ymin = min(y) - deltaY
    ymax = max(y) + deltaY

    # Create meshgrid
    xx, yy = np.mgrid[xmin:xmax:100j, ymin:ymax:100j]

    # We will fit a gaussian kernel using the scipy’s gaussian_kde method
    positions = np.vstack([xx.ravel(), yy.ravel()])
    values = np.vstack([x, y])
    kernel = st.gaussian_kde(values)
    f = np.reshape(kernel(positions).T, xx.shape)

    fig = plt.figure(figsize=(13, 7))
    ax = plt.axes(projection='3d')
    surf = ax.plot_surface(xx, yy, f, rstride=1, cstride=1, cmap='coolwarm', edgecolor='none')
    ax.set_xlabel('x')
    ax.set_ylabel('y')
    ax.set_zlabel('PDF')
    ax.set_title('Surface plot of Gaussian 2D KDE')
    fig.colorbar(surf, shrink=0.5, aspect=5) # add color bar indicating the PDF
    ax.view_init(60, 35)

Мне интересно найти взаимодействие / общий объем (только количество) этих 2 графиков kde:

plot_2d_kde(A)
plot_2d_kde(B)

Кредиты: Код для графики kde от здесь

Ответы [ 2 ]

2 голосов
/ 11 июля 2020

Я считаю, что это то, что вы ищете. Я в основном вычисляю пространство (интеграцию) пересечения (наложения) двух дистрибутивов KDE.

A = pd.DataFrame({'x':[random.uniform(0, 1) for i in range(0,100)], 'y':[random.uniform(0, 1) for i in range(0,100)]})
B = pd.DataFrame({'x':[random.uniform(0, 1) for i in range(0,100)], 'y':[random.uniform(0, 1) for i in range(0,100)]})

# KDE fro both A and B 
kde_a = scipy.stats.gaussian_kde([A.x, A.y])
kde_b = scipy.stats.gaussian_kde([B.x, B.y])

min_x = min(A.x.min(), B.x.min())
min_y = min(A.y.min(), B.y.min())
max_x = max(A.x.max(), B.x.max())
max_y = max(A.y.max(), B.y.max())

print(f"x is from {min_x} to {max_x}")
print(f"y is from {min_y} to {max_y}")
x = [a[0] for a in itertools.product(np.arange(min_x, max_x, 0.01), np.arange(min_y, max_y, 0.01))]
y = [a[1] for a in itertools.product(np.arange(min_x, max_x, 0.01), np.arange(min_y, max_y, 0.01))]

# sample across 100x100 points. 
a_dist = kde_a([x, y])
b_dist = kde_b([x, y])


print(a_dist.sum() / len(x))   # intergral of A
print(b_dist.sum() / len(x))   # intergral of B
print(np.minimum(a_dist, b_dist).sum() / len(x)) # intergral of the intersection between A and B
1 голос
/ 11 июля 2020

Следующий код сравнивает вычисление объема пересечения либо с помощью scipy's dblquad, либо путем взятия среднего значения по сетке.

Примечания:

  • Для 2D-случая (и только с 100 точками выборки) кажется, что дельта должна быть значительно больше 10%. В приведенном ниже коде используется 25%. При дельте 10% расчетные значения для f1 и f2 составляют около 0.90, тогда как теоретически они должны быть 1.0. При дельте 25% эти значения составляют около 0.994.
  • Чтобы приблизить объем простым способом, среднее значение необходимо умножить на площадь (здесь (xmax - xmin)*(ymax - ymin)). Кроме того, чем больше точек сетки учитывается, тем лучше аппроксимация. В приведенном ниже коде используются точки сетки 1000x1000.
  • В Scipy есть несколько специальных функций для вычисления интеграла, например scipy.integrate.dblquad. Это намного медленнее, чем «простой» метод, но немного точнее. Точность по умолчанию не работает, поэтому приведенный ниже код значительно снижает эту точность. (dblquad выводит два числа: приблизительный интеграл и указание ошибки. Чтобы получить только интеграл, в коде используется dblquad()[0].)
  • Тот же подход можно использовать для других измерений. Для «простого» метода создайте сетку большего размера (xx, yy, zz = np.mgrid[xmin:xmax:100j, ymin:ymax:100j, zmin:zmax:100j]). Обратите внимание, что деление на 1000 в каждом измерении приведет к созданию сетки, которая слишком велика для работы.
  • При использовании scipy.integrate, dblquad необходимо заменить на tplquad для 3-х измерений или nquad для размеров N. Это, вероятно, также будет довольно медленным, поэтому необходимо дополнительно снизить точность.
import numpy as np
import pandas as pd
import scipy.stats as st
from scipy.integrate import dblquad

df1 = pd.DataFrame({'x':np.random.uniform(0, 1, 100), 'y':np.random.uniform(0, 1, 100)})
df2 = pd.DataFrame({'x':np.random.uniform(0, 1, 100), 'y':np.random.uniform(0, 1, 100)})

# Extract x and y
x1 = df1['x']
y1 = df1['y']
x2 = df2['x']
y2 = df2['y']
# Define the borders
deltaX = (np.max([x1, x2]) - np.min([x1, x2])) / 4
deltaY = (np.max([y1, y2]) - np.min([y1, y2])) / 4
xmin = np.min([x1, x2]) - deltaX
xmax = np.max([x1, x2]) + deltaX
ymin = np.min([y1, y2]) - deltaY
ymax = np.max([y1, y2]) + deltaY

# fit a gaussian kernel using scipy’s gaussian_kde method
kernel1 = st.gaussian_kde(np.vstack([x1, y1]))
kernel2 = st.gaussian_kde(np.vstack([x2, y2]))

print('volumes via scipy`s dblquad (volume):')
print('  volume_f1 =', dblquad(lambda y, x: kernel1((x, y)), xmin, xmax, ymin, ymax, epsabs=1e-4, epsrel=1e-4)[0])
print('  volume_f2 =', dblquad(lambda y, x: kernel2((x, y)), xmin, xmax, ymin, ymax, epsabs=1e-4, epsrel=1e-4)[0])
print('  volume_intersection =',
    dblquad(lambda y, x: np.minimum(kernel1((x, y)), kernel2((x, y))), xmin, xmax, ymin, ymax, epsabs=1e-4, epsrel=1e-4)[0])

В качестве альтернативы можно вычислить среднее значение по сетке точек и умножить результат на площадь сетки. Обратите внимание, что np.mgrid намного быстрее, чем создание списка с помощью itertools.

# Create meshgrid
xx, yy = np.mgrid[xmin:xmax:1000j, ymin:ymax:1000j]
positions = np.vstack([xx.ravel(), yy.ravel()])
f1 = np.reshape(kernel1(positions).T, xx.shape)
f2 = np.reshape(kernel2(positions).T, xx.shape)
intersection = np.minimum(f1, f2)
print('volumes via the mean value multiplied by the area:')
print('  volume_f1 =', np.sum(f1) / f1.size * ((xmax - xmin)*(ymax - ymin)))
print('  volume_f2 =', np.sum(f2) / f2.size * ((xmax - xmin)*(ymax - ymin)))
print('  volume_intersection =', np.sum(intersection) / intersection.size * ((xmax - xmin)*(ymax - ymin)))

Пример вывода:

volumes via scipy`s dblquad (volume):
  volume_f1 = 0.9946974276169385
  volume_f2 = 0.9928998852123891
  volume_intersection = 0.9046421634401607
volumes via the mean value multiplied by the area:
  volume_f1 = 0.9927873844924111
  volume_f2 = 0.9910132867915901
  volume_intersection = 0.9028999384136771
...