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

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

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

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

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

enter image description here

Ответы [ 2 ]

0 голосов
/ 30 ноября 2018

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

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

Это позволяет сохранить исходные данные в регионахнизкой плотности, но значительно сокращают объем данных для построения в областях высокой плотности.В то же время все функции сохраняются с достаточно плотным биннингом.

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) и построим график обоихИсходные данные и отфильтрованные данные дают:

enter image description here

Вариант использования вопроса с примерно 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()

enter image description here

0 голосов
/ 29 ноября 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-based sample

...