Ультра нечёткие зазоры и проблема группировки островков - PullRequest
0 голосов
/ 15 декабря 2018

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

AvgScore  StdErr    DesiredRank 
65550     2109      1
67188     2050      1
67407     2146      1
67414     1973      1
67486     1889      2
67581     2320      2
67858     1993      2
68509     2029      2
68645     2039      2
68868     2051      2
68902     1943      2
69305     1564      3
69430     2037      3
69509     1594      3
387223    12521     4
389709    12975     4
392200    11344     4
398916    11755     4
399018    11480     5
401144    11021     5
401640    10973     5
403442    10688     5

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

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

Вот наиболее сложныйслучай, показанный в примере: строка 1 имеет строки 2-6 в пределах своего диапазона, и, несмотря на то, что строка 6 также находится в диапазоне строки 1, строка 5 не имеет строки 1 в пределах своего диапазона, поэтому новый ряд должен начинаться в строке 5.

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

Я в 2017 году, но если есть набороснованный на нерекурсивном ответе, который требует 2019, я в порядке с этим.

Ответы [ 2 ]

0 голосов
/ 16 декабря 2018

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

Чтобы продемонстрировать этот набор на основе IS, я добавил GROUP BYколонка.Глубина рекурсии зависит от количества элементов для оценки, а не от количества групп.Все группы обрабатываются одновременно.Этот код был протестирован на моем наборе производственных данных и сравнен с ответами, генерируемыми последовательным циклом по данным, поэтому я знаю, что он работает с большими и более сложными наборами данных.

WITH T AS (
  SELECT * 
    FROM(VALUES ('Type1', 65550  ,2109  ,1),('Type2', 65550  ,2109  ,1),
                ('Type1', 67188  ,2050  ,1),('Type2', 67188  ,2050  ,1),
                ('Type1', 67407  ,2146  ,1),('Type2', 67407  ,2146  ,1),
                ('Type1', 67414  ,1973  ,1),('Type2', 67414  ,1973  ,1),
                ('Type1', 67486  ,1889  ,2),('Type2', 67486  ,1889  ,2),
                ('Type1', 67581  ,2320  ,2),('Type2', 67581  ,2320  ,2),
                ('Type1', 67858  ,1993  ,2),('Type2', 67858  ,1993  ,2),
                ('Type1', 68509  ,2029  ,2),('Type2', 68509  ,2029  ,2),
                ('Type1', 68645  ,2039  ,2),('Type2', 68645  ,2039  ,2),
                ('Type1', 68868  ,2051  ,2),('Type2', 68868  ,2051  ,2),
                ('Type1', 68902  ,1943  ,2),('Type2', 68902  ,1943  ,2),
                ('Type1', 69305  ,1564  ,3),('Type2', 69305  ,1564  ,3),
                ('Type1', 69430  ,2037  ,3),('Type2', 69430  ,2037  ,3),
                ('Type1', 69509  ,1594  ,3),('Type2', 69509  ,1594  ,3)) X(TestType,AvgScore,StdErr,DesiredRank)
), X AS (
  SELECT *,ROW_NUMBER() OVER(PARTITION BY TestType ORDER BY AvgScore) GRow,1 Rnk,AvgScore RAvg, AvgScore+StdErr RMax
  FROM T 
), Y AS (
  SELECT TestType,AvgScore,StdErr,DesiredRank,GRow,Rnk,RAvg,RMax,0 NewRank,0 pravg,0 prmin
    FROM X
   WHERE GRow = 1
   UNION ALL
  SELECT Z.TestType,Z.AvgScore,Z.StdErr,Z.DesiredRank,Z.GRow
        ,CASE WHEN W.NewRank = 1 THEN Y.Rnk+1 ELSE Y.Rnk END Rnk
        ,CASE WHEN W.NewRank = 1 THEN Z.RAvg  ELSE Y.RAvg END RAvg
        ,CASE WHEN W.NewRank = 1 THEN Z.RMax  ELSE Y.RMax END RMin
        ,W.NewRank,Y.RAvg pravg,y.RMax prmin
    FROM Y
   CROSS APPLY (SELECT * FROM X WHERE X.TestType=Y.TestType and X.GRow = Y.GRow+1) Z
   CROSS APPLY (VALUES (CASE WHEN Z.AvgScore <= Y.RMax and Z.AvgScore - Z.StdErr <= Y.RAvg THEN 0 ELSE 1 END)) W(NewRank)
)
SELECT * FROM Y
 ORDER BY TestType,AvgScore;
0 голосов
/ 16 декабря 2018

Это действительно сложный вопрос: сначала я подумал, что могу просто рекурсивно увеличить ранг, если есть определенное пропущенное перекрытие, изучив наивысший ранг за шаг, чтобы у более низких AvgScore было меньше приращений ранга.Но я признал, что рекурсивный элемент рекурсивного CTE не может иметь
- агрегация + GROUP BY
- несколько ссылок на рекурсивный CTE
- вложенный CTE, определенный
, поэтому я отказался от этого направления.Кажется, что данные должны быть «подготовлены» таким образом, чтобы их можно было использовать для простой рекурсии (не может придумать никакого другого решения, кроме рекурсии).
Итак, мое решение состоит в том, чтобы найти самый низкий AvgScore, принадлежащий первомуAvgScore выходит за пределы допустимого диапазона и помечает его как первый элемент нового ранга, а также «перепрыгивает» к этому элементу и повторяется, чтобы в конце все строки в наборе были первой строкой, в которой должен быть назначен новый ранг («первым"подразумевается под сортировкой AvgScore).После этого собираем все строки и ранжируем их.
Так что, если ваш набор называется @UltraFuzzy, вы можете отправить его через пару CTE:

;WITH UltraFuzzyCTE AS (
    SELECT AvgScore, StdErr, AvgScore - StdErr as RangeMIN, AvgScore + StdErr as RangeMAX
    FROM @UltraFuzzy
)
-- SELECT * FROM UltraFuzzyCTE ORDER BY AvgScore
,FirstOutOfRangeCTE AS (
SELECT
    Original.*
    ,MIN (Helper.AvgScore) as FirstOutOfRange
FROM UltraFuzzyCTE as Original
    LEFT OUTER JOIN UltraFuzzyCTE as Helper
        ON Original.RangeMAX < Helper.AvgScore OR Original.AvgScore < Helper.RangeMIN
GROUP BY Original.AvgScore, Original.StdErr, Original.RangeMIN, Original.RangeMAX
)
-- SELECT * FROM FirstOutOfRangeCTE ORDER BY AvgScore
,NewRankFirstMemberCTE AS (
    SELECT * FROM FirstOutOfRangeCTE WHERE AvgScore = (SELECT MIN (AvgScore) FROM FirstOutOfRangeCTE)
    UNION ALL
    SELECT f.*
    FROM NewRankFirstMemberCTE as n
        INNER JOIN FirstOutOfRangeCTE as f ON n.FirstOutOfRange = f.AvgScore
)
-- SELECT * FROM NewRankFirstMemberCTE ORDER BY AvgScore
,RankCTE AS (
SELECT *, 1 as NewRankFirstMember FROM NewRankFirstMemberCTE
UNION ALL
SELECT *, 0 as NewRankFirstMember FROM FirstOutOfRangeCTE WHERE AvgScore NOT IN (SELECT AvgScore FROM NewRankFirstMemberCTE)
)
-- SELECT * FROM RankCTE ORDER BY AvgScore
SELECT *, SUM (NewRankFirstMember) OVER (ORDER BY AvgScore) as Rank
FROM RankCTE
ORDER BY AvgScore

Определенно это можно простодля отладки я использовал SELECT *, но ненужные поля можно было бы выбросить - и использовать меньше CTE.Закомментированный материал для пошагового анализа.

...