Как я могу выполнить подвыборку массива в соответствии с его плотностью? (Удалите частые значения, оставьте редкие)

У меня есть проблема: я хочу построить распределение данных, где некоторые значения встречаются часто, а другие довольно редко. Общее количество очков составляет около 30 000. Рендеринг такого сюжета, как png или (не дай бог) pdf, занимает вечность, а pdf слишком велик для отображения.

Итак, я хочу выполнить подвыборку данных только для графиков. Чего я хотел бы добиться, так это удалить много точек, где они перекрываются (где плотность высока), но сохранить те, где плотность низкая, почти с вероятностью 1.

Теперь numpy.random.choice позволяет указать вектор вероятностей, который я вычислил в соответствии с гистограммой данных с некоторыми изменениями. Но я не могу сделать свой выбор, чтобы редкие очки действительно сохранялись.

Я приложил изображение данных; правый хвост распределения имеет на порядок меньше точек, поэтому я хотел бы сохранить их. Данные трехмерные, но плотность исходит только из одного измерения, поэтому я могу использовать его как меру того, сколько точек находится в данном месте.

введите здесь описание изображения


person oarfish    schedule 29.11.2018    source источник


Ответы (2)


Рассмотрим следующую функцию. Он будет объединять данные в равные ячейки вдоль оси и

  • если в корзине есть одна или две точки, взять эти точки,
  • если в корзине больше точек, возьмите минимальное и максимальное значение.
  • добавьте первую и последнюю точку, чтобы убедиться, что используется один и тот же диапазон данных.

Это позволяет сохранить исходные данные в областях с низкой плотностью, но значительно уменьшить объем данных для построения в областях с высокой плотностью. При этом все признаки сохраняются при достаточно плотном биннинге.

import numpy as np; np.random.seed(42)

def filt(x,y, bins):
    d = np.digitize(x, bins)
    xfilt = []
    yfilt = []
    for i in np.unique(d):
        xi = x[d == i]
        yi = y[d == i]
        if len(xi) <= 2:
            xfilt.extend(list(xi))
            yfilt.extend(list(yi))
        else:
            xfilt.extend([xi[np.argmax(yi)], xi[np.argmin(yi)]])
            yfilt.extend([yi.max(), yi.min()])
    # prepend/append first/last point if necessary
    if x[0] != xfilt[0]:
        xfilt = [x[0]] + xfilt
        yfilt = [y[0]] + yfilt
    if x[-1] != xfilt[-1]:
        xfilt.append(x[-1])
        yfilt.append(y[-1])
    sort = np.argsort(xfilt)
    return np.array(xfilt)[sort], np.array(yfilt)[sort]

Чтобы проиллюстрировать концепцию, давайте воспользуемся некоторыми игрушечными данными.

x = np.array([1,2,3,4, 6,7,8,9, 11,14, 17, 26,28,29])
y = np.array([4,2,5,3, 7,3,5,5, 2, 4,  5,  2,5,3])
bins = np.linspace(0,30,7)

Затем вызов xf, yf = filt(x,y,bins) и построение как исходных данных, так и отфильтрованных данных дает:

введите здесь описание изображения

Вариант использования вопроса с примерно 30000 точек данных будет показан ниже. Использование представленной методики позволит сократить количество точек на графике с 30000 до примерно 500. Это число, конечно, будет зависеть от используемого биннинга - здесь 300 бинов. В этом случае функция вычисляет ~10 мс. Это не супербыстро, но все же большое улучшение по сравнению с построением всех точек.

import matplotlib.pyplot as plt

# Generate some data
x = np.sort(np.random.rayleigh(3, size=30000))
y = np.cumsum(np.random.randn(len(x)))+250
# Decide for a number of bins
bins = np.linspace(x.min(),x.max(),301)
# Filter data
xf, yf = filt(x,y,bins) 

# Plot results
fig, (ax1, ax2, ax3) = plt.subplots(nrows=3, figsize=(7,8), 
                                    gridspec_kw=dict(height_ratios=[1,2,2]))

ax1.hist(x, bins=bins)
ax1.set_yscale("log")
ax1.set_yticks([1,10,100,1000])

ax2.plot(x,y, linewidth=1, label="original data, {} points".format(len(x)))

ax3.plot(xf, yf, linewidth=1, label="binned min/max, {} points".format(len(xf)))

for ax in [ax2, ax3]:
    ax.legend()
plt.show()

введите здесь описание изображения

person ImportanceOfBeingErnest    schedule 30.11.2018
comment
Я хотел бы лично пообщаться с @ImportanceOfBeingErnest bbands gmail - person John; 31.12.2018

Одним из возможных подходов является использование оценки плотности ядра (KDE) для построения предполагаемого распределения вероятностей данных. , затем выполните выборку в соответствии с обратной оценочной плотностью вероятности каждой точки (или какой-либо другой функцией, которая становится меньше, чем больше оценочная плотность вероятности). Существует несколько инструментов для вычисления (KDE) в Python., простой — scipy.stats.gaussian_kde. Вот пример идеи:

import numpy as np
import scipy.stats
import matplotlib.pyplot as plt

np.random.seed(100)
# Make some random Gaussian data
data = np.random.multivariate_normal([1, 1], [[1, 0], [0, 1]], size=1000)
# Compute KDE
kde = scipy.stats.gaussian_kde(data.T)
# Choice probabilities are computed from inverse probability density in KDE
p = 1 / kde.pdf(data.T)
# Normalize choice probabilities
p /= np.sum(p)
# Make sample using choice probabilities
idx = np.random.choice(np.arange(len(data)), size=100, replace=False, p=p)
sample = data[idx]
# Plot
plt.figure()
plt.scatter(data[:, 0], data[:, 1], label='Data', s=10)
plt.scatter(sample[:, 0], sample[:, 1], label='Sample', s=7)
plt.legend()

Выход:

Пример на основе KDE

person jdehesa    schedule 29.11.2018