Расчет двумерного совместного распределения вероятностей

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

% Data
N = 1e5;    % number of points
xy = rand(N, 2);    % coordinates of points
xy(randi(2*N, 100, 1)) = 0;    % add some points on one side
xy(randi(2*N, 100, 1)) = 1;    % add some points on the other side
xy(randi(N, 100, 1), :) = 0;    % add some points on one corner
xy(randi(N, 100, 1), :) = 1;    % add some points on one corner
inds= unique(randi(N, 100, 1)); xy(inds, :) = repmat([0 1], numel(inds), 1);    % add some points on one corner
inds= unique(randi(N, 100, 1)); xy(inds, :) = repmat([1 0], numel(inds), 1);    % add some points on one corner

% Intervals for rectangles
K1 = ceil(sqrt(N/5));    % number of intervals along x
K2 = K1;    % number of intervals along y
int_x = [0:(1 / K1):1, 1+eps];    % intervals along x
int_y = [0:(1 / K2):1, 1+eps];    % intervals along y

% First approach
tic
count_cells = zeros(K1 + 1, K2 + 1);
for k1 = 1:K1+1
  inds1 = (xy(:, 1) >= int_x(k1)) & (xy(:, 1) < int_x(k1 + 1));
  for k2 = 1:K2+1
    inds2 = (xy(:, 2) >= int_y(k2)) & (xy(:, 2) < int_y(k2 + 1));
    count_cells(k1, k2) = sum(inds1 .* inds2);
  end
end
toc
% Elapsed time is 46.090677 seconds.

% Second approach
tic
count_again = zeros(K1 + 2, K2 + 2);
for k1 = 1:K1+1
  inds1 = (xy(:, 1) >= int_x(k1));
  for k2 = 1:K2+1
    inds2 = (xy(:, 2) >= int_y(k2));
    count_again(k1, k2) = sum(inds1 .* inds2);
  end
end
count_again_fix = diff(diff(count_again')');
toc
% Elapsed time is 22.903767 seconds.

% Check: the two solutions are equivalent
all(count_cells(:) == count_again_fix(:))

Как я могу сделать это более эффективно с точки зрения времени, памяти и, возможно, избегая циклов?

EDIT --> Я тоже только что нашел это, это лучшее решение, найденное до сих пор:

tic
count_cells_hist = hist3(xy, 'Edges', {int_x int_y});
count_cells_hist(end, :) = []; count_cells_hist(:, end) = [];
toc
all(count_cells(:) == count_cells_hist(:))
% Elapsed time is 0.245298 seconds.

но для этого требуется панель инструментов статистики.

EDIT --> Решение для тестирования, предложенное chappjc

tic
xcomps = single(bsxfun(@ge,xy(:,1),int_x));
ycomps = single(bsxfun(@ge,xy(:,2),int_y));
count_again = xcomps.' * ycomps; %' 143x143 = 143x1e5 * 1e5x143
count_again_fix = diff(diff(count_again')');
toc
% Elapsed time is 0.737546 seconds.
all(count_cells(:) == count_again_fix(:))

person Community    schedule 02.11.2013    source источник
comment
Возможный дубликат stackoverflow.com/questions/18639518/   -  person Luis Mendo    schedule 02.11.2013
comment
Я также проверяю stackoverflow.com/questions/16313949/ - я не уверен, можно ли использовать hist3 для получения того же результата.   -  person    schedule 03.11.2013
comment
@LuisMendo - это очень подробный ответ на другой вопрос, и он правильно связан здесь. Однако другой вопрос не был конкретным и не содержал кода, поэтому он был закрыт. Итак, я думаю, что вопрос Франческо здесь требует ответов для хорошей попытки решить проблему. Определенный +1 к вашему хорошо продуманному решению другого вопроса. Просто мои 2 цента. :)   -  person chappjc    schedule 03.11.2013
comment
@chappjc Да, поскольку другой вопрос был закрыт, имеет смысл ответить здесь.   -  person Luis Mendo    schedule 03.11.2013
comment
@francesco Если вы используете single вместо double в моем решении, оно работает в два раза быстрее и не должно быть проблемой, поскольку элементы матрицы равны только 0 и 1.   -  person chappjc    schedule 03.11.2013
comment
@Luis, я протестировал ваше решение по предоставленной ссылке - оно не возвращает запрошенный результат, оно очень медленное и также требует много памяти (!). Возможно, я ошибся (?)   -  person    schedule 03.11.2013
comment
@francesco Разве это не сильно ускорило сингл? Может быть, потому что я тестировал на старой версии MATLAB. Кстати, остерегайтесь вносить более 10 правок, когда право собственности на вопрос возвращается к сообществу.   -  person chappjc    schedule 03.11.2013
comment
@chappjc: мой предыдущий комментарий был о решении, предложенном Луисом по ссылке, указанной выше. Использование сингла действительно улучшает ваше решение, особенно для N›1e5. Что означает остерегаться повторения более 10 правок, когда право собственности на вопрос возвращается к сообществу?   -  person    schedule 03.11.2013
comment
Поскольку на этот вопрос все еще есть ответы, я решил опубликовать еще один, используя accumarray. Эта функция предназначена для таких вещей и работает чрезвычайно быстро; все, что вам нужно сделать, это собрать ваши данные.   -  person chappjc    schedule 04.11.2013


Ответы (3)


Улучшение рассматриваемого кода

Ваши циклы (и вложенный скалярный продукт) можно устранить с помощью bsxfun и матричного умножения следующим образом:

xcomps = bsxfun(@ge,xy(:,1),int_x);
ycomps = bsxfun(@ge,xy(:,2),int_y);
count_again = double(xcomps).'*double(ycomps); %' 143x143 = 143x1e5 * 1e5x143
count_again_fix = diff(diff(count_again')');

Шаг умножения выполняет операции И и суммирования, выполненные в sum(inds1 .* inds2), но без зацикливания на матрице плотности. EDIT: если вы используете single вместо double, время выполнения сокращается почти вдвое, но не забудьте преобразовать свой ответ в double или что-то еще, что требуется для остальной части кода. На моем компьютере это занимает около 0,5 секунды.

Примечание. В rot90(count_again/size(xy,1),2) у вас есть CDF, а в rot90(count_again_fix/size(xy,1),2) — PDF.

Использование массива

Другой подход заключается в использовании accumarray для построения совместной гистограммы после объединения данных.

Начиная с int_x, int_y, K1, xy и т. д.:

% take (0,1) data onto [1 K1], following A.Dondas approach for easy comparison
ii = floor(xy(:,1)*(K1-eps))+1; ii(ii<1) = 1; ii(ii>K1) = K1;
jj = floor(xy(:,2)*(K1-eps))+1; jj(jj<1) = 1; jj(jj>K1) = K1;

% create the histogram and normalize
H = accumarray([ii jj],ones(1,size(ii,1)));
PDF = H / size(xy,1); % for probabilities summing to 1

На моем компьютере это занимает около 0,01 сек.

Результат такой же, как у А. Донды, преобразованного из разреженного в полное (full(H)). Хотя, как указал А. Донда, правильно иметь размеры K1xK1, а не размер count_again_fix в коде OP, который был K1+1xK1+1.

Чтобы получить CDF, я считаю, что вы можете просто применить cumsum к каждой оси PDF.

person chappjc    schedule 02.11.2013
comment
+ Работает! Спасибо! Я пытаюсь сделать это с помощью hist3. - person ; 02.11.2013
comment
Примечание для всех: я не обязательно ищу решение общего вопроса о совместном распределении вероятностей, а скорее способ изменить код Франческо, чтобы сделать это более эффективно с точки зрения времени, памяти и, возможно, избегая циклов. Я думаю, что здесь есть тонкая грань, и она сводится к масштабу и качеству двух вопросов. Я сейчас выйду на улицу. :п - person chappjc; 03.11.2013
comment
Использование hist3 кажется лучшим вариантом, если доступна панель инструментов статистики, в противном случае решение, предложенное chappjc, является лучшим альтернативным решением, которое я тестировал до сих пор. - person ; 03.11.2013
comment
Ваше решение с использованием accumarray действительно очень быстрое - оно сравнимо с моей функцией mex! Хотя мне нужны также крайние значения, 0 и 1, поэтому я думаю, что размер матрицы должен быть (K1+1)x(K2+1) - учтите, что я использую ребра, а не корзины. - person ; 04.11.2013

Я написал простую функцию mex, которая очень хорошо работает, когда N велико. Обман, конечно, но все же...

Функция

#include "mex.h"

void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[])
{
    unsigned long int hh, ctrl;       /*  counters                       */
    unsigned long int N, m, n;        /*  size of matrices               */
    unsigned long int *xy;            /*  data                           */
    unsigned long int *count_cells;   /*  joint frequencies              */
    /*  matrices needed */
    mxArray *count_cellsArray;

/*  Now we need to get the data */
    if (nrhs == 3) {
        xy = (unsigned long int*) mxGetData(prhs[0]);
        N = (unsigned long int) mxGetM(prhs[0]);
        m = (unsigned long int) mxGetScalar(prhs[1]);
        n = (unsigned long int) mxGetScalar(prhs[2]);
    }

/*  Then build the matrices for the output */
    count_cellsArray = mxCreateNumericMatrix(m + 1, n + 1, mxUINT32_CLASS, mxREAL);
    count_cells = mxGetData(count_cellsArray);
    plhs[0] = count_cellsArray;

    hh = 0; /* counter for elements of xy */
    /* for all points from 1 to N */
    for(hh=0; hh<N; hh++) {
        ctrl = (m + 1) * xy[N + hh] + xy[hh];
        count_cells[ctrl] = count_cells[ctrl] + 1;
    }
}

Его можно сохранить в файле «joint_dist_points_2D.c», а затем скомпилировать:

mex joint_dist_points_2D.c

И проверьте это:

% Data
N = 1e7;    % number of points
xy = rand(N, 2);    % coordinates of points
xy(randi(2*N, 1000, 1)) = 0;    % add some points on one side
xy(randi(2*N, 1000, 1)) = 1;    % add some points on the other side
xy(randi(N, 1000, 1), :) = 0;    % add some points on one corner
xy(randi(N, 1000, 1), :) = 1;    % add some points on one corner
inds= unique(randi(N, 1000, 1)); xy(inds, :) = repmat([0 1], numel(inds), 1);    % add some points on one corner
inds= unique(randi(N, 1000, 1)); xy(inds, :) = repmat([1 0], numel(inds), 1);    % add some points on one corner

% Intervals for rectangles
K1 = ceil(sqrt(N/5));    % number of intervals along x
K2 = ceil(sqrt(N/7));    % number of intervals along y
int_x = [0:(1 / K1):1, 1+eps];    % intervals along x
int_y = [0:(1 / K2):1, 1+eps];    % intervals along y

% Use Statistics Toolbox: hist3
tic
count_cells_hist = hist3(xy, 'Edges', {int_x int_y});
count_cells_hist(end, :) = []; count_cells_hist(:, end) = [];
toc
% Elapsed time is 4.414768 seconds.

% Use mex function
tic
xy2 = uint32(floor(xy ./ repmat([1 / K1, 1 / K2], N, 1)));
count_cells = joint_dist_points_2D(xy2, uint32(K1), uint32(K2));
toc
% Elapsed time is 0.586855 seconds.

% Check: the two solutions are equivalent
all(count_cells_hist(:) == count_cells(:))
person Community    schedule 03.11.2013
comment
Хороший вклад! Но MEX — это своего рода читерство, да. ;) Тем не менее, я использовал файл MEX при создании совместных PDF-файлов для своего исследования, поэтому, в конце концов, я соглашусь, что это правильный путь. Однако для этих N=1e7 тестовых данных мой обновленный accumarray подход занимает 1,1 секунды на моем ПК, так что это может быть хорошей общей альтернативой, не требующей наборов инструментов. - person chappjc; 04.11.2013
comment
Я согласен! Я протестировал ваше решение с помощью accumarray, и оно работает быстро даже с N=3e7! Вступительная часть! - person ; 04.11.2013

ответ chappjc и использование hist3 — все это хорошо, но, поскольку я хотел иметь что-то подобное некоторое время назад и по какой-то причине не нашел hist3, я написал его сам и решил опубликовать его здесь в качестве бонуса. Он использует sparse для фактического подсчета и возвращает результат в виде разреженной матрицы, поэтому он может быть полезен для работы с мультимодальным распределением, когда разные режимы находятся далеко друг от друга, или для тех, у кого нет панели инструментов статистики.

Применение к данным Франческо:

K1 = ceil(sqrt(N/5));
[H, xs, ys] = hist2d(xy(:, 1), xy(:, 2), [K1 K1], [0, 1 + eps, 0, 1 + eps]);

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

Вот функция:

функция [H, xs, ys] = hist2d(x, y, n, ax)

% plot 2d-histogram as an image
%
% hist2d(x, y, n, ax)
% [H, xs, ys] = hist2d(x, y, n, ax)
%
% x:    data for horizontal axis
% y:    data for vertical axis
% n:    how many bins to use for each axis, default is [100 100]
% ax:   axis limits for the plot, default is [min(x), max(x), min(y), max(y)]
% H:    2d-histogram as a sparse matrix, indices 1 & 2 correspond to x & y
% xs:   corresponding vector of x-values
% ys:   corresponding vector of y-values
%
% x and y have to be column vectors of the same size. Data points
% outside of the axis limits are allocated to the first or last bin,
% respectively. If output arguments are given, no plot is generated;
% it can be reproduced by "imagesc(ys, xs, H'); axis xy".


% defaults
if nargin < 3
    n = [100 100];
end
if nargin < 4
    ax = [min(x), max(x), min(y), max(y)];
end

% parameters
nx = n(1);
ny = n(2);
xl = ax(1 : 2);
yl = ax(3 : 4);

% generate histogram
i = floor((x - xl(1)) / diff(xl) * nx) + 1;
i(i < 1) = 1;
i(i > nx) = nx;
j = floor((y - yl(1)) / diff(yl) * ny) + 1;
j(j < 1) = 1;
j(j > ny) = ny;
H = sparse(i, j, ones(size(i)), nx, ny);

% generate axes
xs = (0.5 : nx) / nx * diff(xl) + xl(1);
ys = (0.5 : ny) / ny * diff(yl) + yl(1);

% possibly plot
if nargout == 0
    imagesc(ys, xs, H')
    axis xy
    clear H xs ys
end
person A. Donda    schedule 03.11.2013
comment
Функция великолепна, но результат не совсем тот же — я думаю, края справа обрабатываются по-разному. Я пытаюсь понять, могу ли я исправить это соответствующим образом. - person ; 03.11.2013
comment
Спасибо! Может быть, это потому, что индексы 1 и 2 соответствуют y и x? Я сделал это так, потому что именно так imagec хочет вводить данные, но, возможно, это была плохая идея. В этом случае транспозиция должна исправить это. - person A. Donda; 03.11.2013
comment
Кроме того, ваше решение hist3 создает матрицу 143 x 143, тогда как K1 = K2 = 142, и моя функция соответственно создает матрицу 142 x 142. - person A. Donda; 03.11.2013
comment
@francesco, я изменил свою функцию, чтобы выдавать результат с естественным порядком координат. Оставшаяся разница связана с тем, что hist3 с указанием «Границы» игнорирует точки данных, лежащие снаружи, в то время как моя функция считает их в направлении бинов поля. Его вывод идентичен выходу hist3, если он вызывается следующим образом: hist3(xy, 'Ctrs', {xs ys}), где xs и ys — центры бинов, возвращаемые моей функцией. Спасибо, что указали на эти несоответствия! - person A. Donda; 03.11.2013
comment
@A.Donda Это хороший способ. Вместо того, чтобы использовать sparse для подсчета, MATLAB accumarray весьма удобен для накопления таких группированных данных. Я разместил второе решение в своем ответе только для полноты картины. - person chappjc; 04.11.2013