Поскольку на прошлой неделе рассылка не вышла, на этой неделе
- два выпуска подряд.
Подсчёт уникальных значений по большому числу переменных
"Тема номера" - подсчёт уникальных значений по нескольким
переменным. Количество переменных и разнообразие вариантов значений, вообще
говоря, неограничено. Моделью проблемной ситуации может быть, например, случай
повторных измерений: имеется 400 наблюдений множества объектов. При каждом наблюдении
объект может находиться в одном однозначно идентифицируемом состоянии из множества
состояний. Каждый раз фиксируется числовой код этого состояния. Одним из результатов
данного исследования может быть информация о количестве уникальных состояний,
которые мы пронаблюдали для каждого объекта.
Возьмём за основу макрос из файла Число
уникальных значений по 400 переменным.SPS
в коллекции Рейналя. Автор макроса: Raynald Levesque, макрос написан 16.08.2002.
Реализация подобной задачи в форме макроса предполагает, в общем-то, многократное
использование этой процедуры с разными параметрами. Допустим, что подобные вычисления
нам требуется провести 1 раз для фиксированного числа переменных с фиксированными
именами. В этом случае мы можем не использовать макроязык и обойтись обычным
синтаксисом.
Программа получилась несложная, однако "с изюминкой"
в виде алгоритма поиска и отсева значений-дубликатов. Кроме того, в данном примере
нам встретятся следующие технические моменты, на которые вы, возможно, захотите
обратить внимание:
1. Особенность задания пропущенных значений через команду DATA
LIST;
2. Использование векторной адресации переменных;
3. Использование условных операторов (DO IF) и циклов LOOP, в
том числе - вложенных циклов, обеспечивающих попарное сравнение значений переменных;
4. Использование функции NMISS для подсчёта пропущенных значений
в наборе переменных;
5. Удаление лишних переменных с помощью команды ADD FILES.
Объявим в качестве примера данных 4 переменных (4, а не 400,
что не принципиально в данном случае) и зададим их значения с помощью комбинаций
команд DATA LIST и BEGIN DATA - END DATA. Здесь имя последней переменной - varn,
символизирующее, что переменных, содержащих повторные наблюдения может быть
произвольное количество. Как видно из данных, не каждый объект наблюдался все
4 раза. Так, например, в первом и третьем наблюдениях состояние объекта номер
5 не было установлено. Пропущенное значение вводится как ".". Программа
встречает нечисловое значение, пытается его приписать числовой (по умолчанию)
переменной, у неё ничего не выходит, о чём она предупреждает пользователя. В
результате - на месте пропуска в файле данных SPSS оказывается именно пропуск.
Команда LIST выводит в окно результатов введённые данные.
Общая идея алгоритма. Создадим новые n (4) переменных,
куда скопируем значения исходных. Затем для каждого объекта в отдельности берём
k-ю исходную переменную (k={1..n-1}). Если соответствующая ей k-я новая переменная
не содержит пропуска, сравниваем значение k-й старой переменной с k+1, k+2 и
т.д., k+(n-k) новыми переменными. Если значения совпадают, значение k+...-й
новой переменной обнуляем (объявляем пропущенным значением). Если же k-я новая
переменная содержит пропуск, значит пропуск содержала и k-я старая переменная,
либо непропущенное значение k-й старой переменной уже было обработано. В обоих
случаях дополнительных проверок на равенство значений можно не делать. К исходу
алгоритма для каждого объекта новые переменные будут содержать либо пропуски,
либо числовые значения, но без повторов в других переменных. На заключительном
этапе мы для каждого объекта считаем количество непропущенных значений по всем
новым переменным, что даёт нам количество уникальных значений.
Программная реализация
Первый этап - продублировать значения исходных 4-х переменных
в новые, рабочие переменные. Задействуем векторную адресацию. Объявим переменные
с var1 по varn вектором (в файле данных они должны размещаться рядом, одна к
другой), и одновременно создадим вектор из 4-х новых переменных (val1 - val4)
- обратите внимание, как различаются правила задания векторов для уже существующих
и новых переменных в команде VECTOR. С помощью цикла LOOP с использованием "невидимой"
индексной переменной #cnt мы осуществляем копирование значений их исходных переменны
в новые (команда COMPUTE).
VECTOR v=var1 TO varn /val(4).
LOOP #cnt=1 TO 4.
COMPUTE val(#cnt)=v(#cnt).
END LOOP.
Второй этап. Для каждого объекта выполняем цикл LOOP с индексом
#cnt1 от 1 до 3 (до n-1, в общем случае). Этот индекс используется для адресации
переменных, т.е. выражение val(#cnt1) означает, соответственно, переменную val1,
val2, или val3, в зависимости от значения индекса #cnt1. Если новая переменная
val(#cnt1) содержит пропуск, это может говорить о двух вещах. Либо исходная
старая переменная содержала пропуск, либо непропущенное значение из старой переменной
уже встречалось раньше в этом наблюдении и было обнулено в новой переменной
val(#cnt1), а также во всех последующих новых переменных. И в том, и в другом
случае, дополнительных действий можно не предпринимать, а переходить к рассмотрению
следующих переменных.
Если же в переменной val(#cnt1) что-то содержится, мы запускаем
второй цикл, с индексом #cnt2, от #cnt1+1 до 4. Так мы обеспечим себе возможность
сравнить значения всех новых переменных, стоящих справа от текущей старой переменной
со значением текущей старой переменной. Команда условных вычислений IF объявляет
пропусками все повторы текущего значения старой переменной. Оно остаётся нетронутым
только в #cnt1-й новой переменной. Присвоение пропущенного значения достигается
с помощью системной переменной $SYSMIS, которая для каждого наблюдения имеет
пропущенное значение.
Можно убедиться, что алгоритм работает во всех частных случаях
(когда для некоторого объекта все наблюдения являются пропущенными, или единственное
непропущенное значение находится в первом или последнем наблюдении, когда состояние
объекта при всех наблюдениях одинаково, или наоборот - при всех наблюдениях
уникально).
LOOP #cnt1 = 1 TO 3.
DO IF ~MISSING(val(#cnt1)).
LOOP #cnt2 = #cnt1 + 1 TO 4.
IF val(#cnt2)=v(#cnt1) val(#cnt2)=$SYSMIS.
END LOOP.
END IF.
END LOOP.
Последний функциональный этап - подсчёт числа непропущенных (читай
- уникальных) значений по всем новым переменным. Делаем это методом от обратного.
Функция NMISS возвращает количество пропусков по заданному списку переменных.
Мы вычитаем из общего числа повторных наблюдений (4) количество обнаруженных
пропусков. Переменная distinct, таким образом, будет содержать искомое число
уникальных состояний объекта по 4-м повторным наблюдениям.
COMPUTE distinct=4 - NMISS(val1 TO val4).
Заключительная команда ADD FILES, содержащая лишь одну действенную
подкоманду DROP убирает ненужные более временные переменные val1 - val4. Это,
не вполне характерное использование ADD FILES (которая предназначена, вообще
говоря, для слияния файлов данных) в данном случае дало нам элегантную возможность
выполнить отложенные вычисления и убрать лишние переменные одной командой. Если
бы мы захотели сделать это с помощью команды DELETE VARIABLES, потребовалось
бы предварительно выполнить отложенные вычисления командой EXECUTE. Мелочь,
конечно, но приятно...
ADD FILES FILE=* /DROP=val1 TO val4.
Отметьте, что нигде раньше отложенные вычисления не запускались,
т.е. все переменные val1-val4 и distinct фактически ещё не были вычислены до
запуска команды ADD FILES. Не было бы ошибкой вставить команду EXECUTE перед
последним COMPUTE, но попытка поместить её между синтаксисом первого и второго
этапов закончилась бы неудачей всего решения. Выполнение отложенных вычислений
разрушило бы векторную адресацию, заданную вначале и на втором этапе мы бы не
смогли ей воспользоваться. Пришлось бы её создавать вновь.