Вычисление процентиля, частоты и частоты процентиля для значений в Postgres 11 - PullRequest
2 голосов
/ 20 июня 2019

Я новичок в современном SQL и почти уверен, что что-то слишком усложнил. Я пытаюсь разбить значения на процентили как по самим значениям, так и по их частоте. Так что, если у меня есть 1000 записей с 100 различными числами, есть диапазон значений, а также диапазон частот, с которыми эти значения встречаются. То, что я хочу получить для каждого значения:

  • Само значение.
  • Его процентиль в диапазоне значений
  • Это количество (частота)
  • процентиль частоты

Я использую игрушечные таблицы, чтобы поэкспериментировать с 1000 записями, заполненными случайными значениями из www.mockaroo.com. Мои настоящие таблицы имеют сотни тысяч или миллионы строк. Смысл всего этого заключается в том, чтобы прикрепить процентили и т. Д. К концу каждой строки в представлении для подачи платформы визуализации данных, которая не очень хороша для процентилей. Чтобы было понятно, если я начинаю с 1К записей в моей таблице, запросы должны заканчиваться 1К строк.

CREATE TABLE IF NOT EXISTS mock (
    n integer
);

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

-- Get the value, count, and value percentile.
with
value_counts as (
select n as value,
    count(*) as frequency_count,
    ntile(10) over (order by n) as value_decile
  from mock
group by n
),

-- Now add the frequency percentile.
frequency_analysis as (
select value,
    ntile(10) over (order by frequency_count) as frequency_decile
    from value_counts
),

-- Don't need this CTE, just making things readable.
value_information as (
select value_counts.value,
    value_counts.value_decile,
    value_counts.frequency_count,
    frequency_analysis.frequency_decile
from   value_counts
join frequency_analysis on (frequency_analysis.value= value_counts.value)
)

select * from value_information;

Итак, я думаю, что это работает .... но у меня нет одного столбца для проверки, у меня есть лотов столбцов для генерации счетчика частоты, процентиля частоты и процентиля значения на. Кажется, это должен быть обычный статистический запрос для выполнения, но мне трудно понять, как это сделать аккуратно в Postgres. Пара моментов, прежде чем попасть в таблицу с двумя столбцами:

  • Я использую CTE для разборчивости, для создания агрегатов, которые затем можно будет агрегировать в более поздних CTE, и для создания небольшого продукта, к которому можно присоединиться. Все быстро с записями в 1 КБ, но, возможно, не так много с записями в 20 миллионов.

  • Я на Postgres 11 и не перейду на PG 12 до тех пор, пока он не появится. Таким образом, нет никакого беспокойства о том, как осуществляются CTE. Поведение PG 11 - это именно то, чего я хочу.

  • Я использую ntile (), потому что на самом деле очень просто динамически регулировать количество бинов, поэтому 10 вместо 100. Я не уверен, стоит ли использовать вместо этого width_bucket, процентный_конт / процентный_диск. Я только что нашел width_bucket этим утром, так что, возможно, есть еще один встроенный способ сделать процентили. (Помимо ручного кодирования.)

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

Хорошо, теперь пример более реалистичной таблицы с более значимыми именами полей:

CREATE TABLE IF NOT EXISTS ascendco.mock2 (
    num_inst integer,
    points integer
);

Опять 1000 записей с разными значениями в двух столбцах. Эти два столбца не имеют отношения в вычислениях, они просто находятся в одной строке, и я хочу, чтобы агрегаты были добавлены в конец. Все это очень похоже на операцию с оконной функцией, но я не хочу повторять работу над 10M строками. Итак, как это сделать для двух + столбцов? У меня сегодня 2-5 в большинстве моих столов, и это будет расти. Я сталкиваюсь с тем, что GROUP BY предназначен для обеспечения одного уровня агрегации, мне нужно отдельное агрегирование для каждого столбца. Есть ли способ сделать это, кроме длинной формы, которую я опробовал ниже?

-- Get the num_inst count and the decile for the value.
with 
num_inst_distinct_counts as (
  select num_inst,
         count(*) as num_inst_frequency,
         ntile(10) over (order by num_inst) as value_decile
    from mock2 
group by num_inst),

-- Extend the previous CTE with the decile for the value's frequency. 
num_inst_information as (
    select *,
           ntile(10) over (order by num_inst_frequency) as frequency_decile
      from num_inst_distinct_counts
),

-- Get the points count and the decile for the value.
points_distinct_counts as (
  select points,
         count(*) as points_frequency,
         ntile(10) over (order by points) as value_decile
    from mock2 
group by points),

-- Extend the previous CTE with the decile for the value's frequency. 
points_information as (
    select *,
           ntile(10) over (order by points_frequency) as frequency_decile
      from points_distinct_counts
)

-- Put it all togehter. I could have used more general names in the CTEs, but this makes the output clearer
select mock2.num_inst,
       num_inst_information.value_decile as num_inst_value_decile,
       num_inst_information.frequency_decile as num_inst_frequency_decile,

       mock2.points,
       points_information.value_decile as points_value_decile,
       points_information.frequency_decile as points_frequency_decile

from mock2
join num_inst_information on (num_inst_information.num_inst = mock2.num_inst)
join points_information   on (points_information.points = mock2.points)

order by 1;

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

Спасибо за любую помощь!


Проснулся и дал ему еще один ход. Ну, я не понял что-то о count (*) и прочем. Вы можете вкладывать агрегаты, хотя бы немного. Пересмотренные программы, приведенные ниже, дают те же результаты, что и оригиналы, так что это немного лучше. Я до сих пор интуитивно понимаю, что мне не хватает чего-то очевидного, что могло бы сделать все это значительно проще. Есть идеи? Для записи вот новые версии запросов с одним и двумя столбцами. Эти длинные операторы ORDER BY в нижней части каждого есть только для того, чтобы я мог легко получать и анализировать результаты исходного и пересмотренного запросов.

Вот новый запрос с одним столбцом, намного короче:

  select distinct n as value,
          ntile(10) over (order by n) as value_decile,
          count(*) as frequency_count,
          ntile(10) over (order by count(*)) as frequency_decile
    from mock
group by n
order by 1,2,3,4

Версия с двумя столбцами использует один CTE на столбец, а затем объединяет все вместе с главной таблицей.

- Получить данные для столбца num_inst.

with 
num_inst_information as (
  select distinct num_inst,
          ntile(10) over (order by num_inst) as value_decile,
          count(*) as frequency_count,
          ntile(10) over (order by count(num_inst)) as frequency_decile
    from mock2
group by num_inst
),

-- Get the details for the points column.
points_information as (
  select distinct points,
          ntile(10) over (order by points) as value_decile,
          count(*) as frequency_count,
          ntile(10) over (order by count(points)) as frequency_decile
    from mock2
group by points
)

-- Get every row in the base table and use the CTEs above for lookups (joins) with the extra data.
select mock2.num_inst,
       num_inst_information.value_decile as num_inst_value_decile,
       num_inst_information.frequency_decile num_inst_frequency_decile,

       mock2.points,
       points_information.value_decile as points_value_decile,
       points_information.frequency_decile as points_frequency_decile

from mock2
left join num_inst_information on (num_inst_information.num_inst = mock2.num_inst)
left join points_information   on (points_information.points = mock2.points)
order by 1,2,3,4,5,6 

Новые версии немного быстрее, что тоже неплохо.


S-Man запросил пример данных и вывод. Справедливо! И спасибо за чтение и хочу помочь. Я создал учетную запись Pastebin с образцами из 1 столбца

https://pastebin.com/embed_js/eUZkBqhA

и данные с 2 столбцами: https://pastebin.com/embed_js/7J4vx850

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

  • Получить исходное значение из каждой записи в выводе. 1M строк в таблице, 1M строк в результате.

  • Для каждой строки добавьте три новых столбца к выводу для каждого «реального» столбца данных:

1) процентиль значения. 2) частота значения (кол.) 3) процентиль частоты.

Так, например,

num_inst Реальные данные из базовой таблицы.

num_inst_value_percentile Процентиль (использованные выше децили, отмечено) для этого num_inst среди всех num_inst в таблице.

num_inst_frequency Как часто это значение появляется во всей таблице? Итак, граф.

num_inst_frequency_percentile Процентиль (использованные выше децили, отмечено) для этого num_inst_frequency среди всех num_inst_frequencies в таблице.

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

На случай, если кому-то интересно, наши данные довольно длинны и их трудно составить. Объединение данных в процентили облегчает вычисление формы данных / распределений. После того, как я закончу с этим, следующим шагом будет выяснить, как использовать оконные функции (я полагаю), чтобы получить размеры диапазона для каждого процентиля.

Надеюсь, это понятнее!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...