Анализ виртуальных сообществ (часть 2 - анализ групп по составу)

Author

Омельченко Д.А.

Краткое содержание предыдущих серий

На прошлых занятиях мы учились делать выгрузку по сообществам через API VK

install.packages("vkR")# эта строчка нужна только тем, кто еще не установил пакет vkR
library(vkR)
#получить ключ
vkOAuth(11111111)# в скобках - id приложения в ВК, которое создавали на предыдущих занятиях, откроется браузер, скопировать ключ до знака &
#установить ключ
setAccessToken(access_token="vk1.a....ТУТ ВАШ КЛЮЧ до - скопировать ДО знака & перед &expires_in=86400&user_id")
# установить версию API
v<-setAPIVersion("5.89")
#Выгрузить информацию об ID пользователей группы (пример по группам кафедры, ИГН и Дома народов Алтайского края) - у вас будут свои группы
kaf<-getGroupsMembersExecute(group_id = 211524072)
ign<-getGroupsMembersExecute(group_id = "ign21")
rnd<-getGroupsMembersExecute("rndaltai")
#выгрузить информацию о пользователях (примеры по кафедре, ИГН и Дому народов Алтайского края)
kaf_info<-getUsersExecute(kaf, fields='sex, bdate, country, city, occupation')
ign_info<-getUsersExecute(ign, fields='sex, bdate, country, city, occupation')
rnd_info<-getUsersExecute(rnd, fields='sex, bdate, country, city, occupation')

Нам нужно сделать обобщающую таблицу с основными сведениями.

Допустим, у нас есть таблицы по трем группам - кафедры, ИГН и Дома народов Алтайского края. Они называются kaf_info, ign_info, rnd_info

Чтобы не делать одну и ту же работу несколько раз, соединим все таблицы в одну большую таблицу, которую назовем info.

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

kaf_info$group<-"Кафедра"
ign_info$group<-"ИГН"
rnd_info$group<-"РНД"

Теперь соединим все таблицы в одну:

info<-rbind(kaf_info,ign_info, rnd_info )

Самый простой анализ заключается в создании одномерных таблиц с частотами с помощью функции table() . Давайте, например, посмотрим, сколько участников в каждой группе.

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

table(info$group)

    ИГН Кафедра     РНД 
   3377     206     677 

Чтобы сделать аналогичный анализ в процентах, можно воспользоваться функцией prop.table(), которой мы как бы оборачиваем нашу исходную таблицу. Чтобы код выглядел лучше, можно таблицу сохранить в качестве отдельного объекта, а уже по нему сделать таблицу с пропорциями:

t<-table(info$group)
#В процентах
prop.table(t)*100

      ИГН   Кафедра       РНД 
79.272300  4.835681 15.892019 

Что, если мы хотим получить двумерную таблицу? Например, посмотреть распределение не только по группе, но и по количеству закрытых профилей?

Функции останутся прежними, только в функцию prop.table() добавим аргумент margin = 2, чтобы проценты рассчитывались по столбцу.

Заметьте, на первом месте у нас целевая переменная info$is_closed, на втором - группирующая info$group:

t2<-table(info$is_closed, info$group)
prop.table(t2, margin = 2)*100
       
             ИГН  Кафедра      РНД
  FALSE 80.54486 77.18447 79.61595
  TRUE  19.45514 22.81553 20.38405

Видим, что количество закрытых профилей примерно одинаково в разных группах - от 19,5% до 22,8%.

Однако, анализировать информацию по каждой переменной не очень удобно. Нам хотелось бы провести комплексный анализ и сделать сводную таблицу по всем переменным. К сожалению, так сделать не получится, хотя бы потому, что некоторые переменные, например, интересы - это поле со свободными ответами и делать по ним таблицу не очень целесообразно, нужно описывать в целом, проводя качественный анализ “вручную”.

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

  • вычислить возраст по дате рождения
  • перекодировать переменные (заменить коды на метки)
  • некоторые переменные являются вложенными (city, occupation, personal, country) то есть это не одна, а несколько переменных в одной группе (идентификатор и название города, тип занятий, название организации и др.) - соответственно, нам нужно эти переменные привести к общему табличному виду.

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

install.packages(c("readr", "lubridate", "dplyr", "tidyr", "forcats", "gtsummary"))

Эти пакеты имеют следующее предназначение:

  • readr - работа с прямоугольными таблицами (импорт из разных источников) и парсинг различных форматов
  • lubridate - работа с временными данными
  • dplyr - всевозможные трансформации данных, такие как отбор переменных, фильтрация наблюдений, перекодировка, группировка, вычисление новых переменных и пр.
  • tidyr - помощь в трансформации данных в “чистый формат”, когда в каждой строке содержится одно наблюдение и в каждом столбце - только одна переменная,
  • forcats - обработка категориальных (факторных) переменных
  • gtsummary - создание красивых таблиц для научных исследований.

Вычисление возраста

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

library(lubridate)
library(readr)

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

Чтобы не потерять исходные данные, результаты перекодировки сохраним в новую переменную - bdate2:

# 1. Конвертируем переменную bdate в дату и сохраняем результаты в новую переменную bdate2
info$bdate2<-readr::parse_datetime(info$bdate, "%d%.%m%.%Y")
Warning: 1786 parsing failures.
row col             expected actual
  2  -- date like %d%.%m%.%Y  15.12
  5  -- date like %d%.%m%.%Y  19.5 
 10  -- date like %d%.%m%.%Y  10.7 
 11  -- date like %d%.%m%.%Y  22.4 
 12  -- date like %d%.%m%.%Y  2.12 
... ... .................... ......
See problems(...) for more details.
# 2.Создадим переменную возраста с помощью функций time_length() и difftime().
info$age<-time_length(difftime(today(), info$bdate2), "years")

То, что появилось предупреждение, ничего страшного, это означает, что программа не смогла правильно распознать все даты.

Посмотрим, что у нас получилось:

summary(info$age)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  14.05   19.70   21.72   26.89   27.79  122.90    2391 

Как видим, у нас довольно большой размах по возрасту - от 17 (почти 18) до 66 лет, средний возраст (mean) - составил 26,8 года, медианный (median)- 21,7 года. Велико и количество пропущенных значений - 2391 случай из 4260. Кроме того, мы видим, что максимальный возраст очень велик - 122,89 года, вряд ли кто-то дожил до 100 лет))).

Давайте заменим на пропущенные все значения возраста, превышающие 100 лет:

info$age[info$age >100] <- NA

Проверим. что у нас получилось:

summary(info$age)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
  14.05   19.67   21.67   26.26   27.45   99.20    2404 

Видим, что теперь есть те, кому 99))) Давайте посмотрим на гистограмму:

hist(info$age, col = "steelblue")

Видим, что есть разные единичные ответы больше 70 лет, поэтому при описании лучше ориентироваться на медианный возраст.

Какие это наблюдения:

which(info$age>70)
[1]  734 1669 2124 2507 3077 3866 4140 4154 4260

Анализ пропущенных значений

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

#Посмотрим количество пропущенных значений по каждому столбцу
colMeans(is.na(info))*100
               id             bdate                id             title 
          0.00000          14.20188          40.11737          40.11737 
               id             title                id              name 
         28.38028          28.38028          35.63380          33.14554 
             type        country_id           city_id     graduate_year 
         33.14554          87.48826          53.75587          55.25822 
              sex        first_name         last_name can_access_closed 
          0.00000           0.00000           0.00000           0.00000 
        is_closed       deactivated             group            bdate2 
          0.00000          97.86385           0.00000          56.12676 
              age 
         56.43192 

Видим, что даже по основным переменным данных сильно не хватает: даже такие переменные, как город проживания, пропущены у 40% пользователей.

Отбор переменных для анализа

Давайте отберем в отдельный набор переменные, которые мы будем представлять в виде отдельной таблицы.

Поскольку при выгрузке создается объект класса vk.users, некоторые функции трансформации, которые мы будем применять (отбор, сортировка, перекодировка), будут недоступны, и нам нужно перевести его в более приемлемый вид.

Наиболее эффективным будет являться формат tibble.

library(tibble)
info<-as_tibble(info)

Теперь уберем вложенность некоторых переменных, так как пока наша таблица представляет собой «матрешку», в которую вложены другие таблицы (это можно увидеть, нажав на голубой кружок с белым треугольником в блоке Environment:

Figure 1: Отображение «вложенности» таблиц друг в друга в блоке рабочего окружения (Environment).

Чтобы убрать, по крайней мере частично, иерархичность таблицы, воспользуемся функцией unnest() из библиотеки tidyr:

library(tidyr)
info<-info %>%
  unnest(cols = c(country, city, occupation),names_sep = "_" ) # cols - столбцы, которые нам необходимо «развернуть».

Сделаем окончательный отбор переменных в таблицу (оставлены только самые важные):

  • группа (group),
  • возраст (age)
  • тип занятости (occupation_type)
  • название страны (country_title)
  • название города(city_title)
library(dplyr)
#отберем в набор самые главные показатели
info<-info %>%
  select(group, age, sex, occupation_type, country_title, city_title)

Перекодировка переменных

Наиболее быстрый способ трансформировать данные (вычислить новые переменные, перекодировать и пр.) - воспользоваться возможностями пакета dplyr из библиотеки tidyverse.

Как понять, как перекодировать, и что означает код? Посмотреть документацию - справочник по API ВК.

Сделаем перекодировку по полу, типу занятости, и переименуем наши переменные на русский язык:

info<-info %>%
  mutate(sex=case_when(
   sex==1~"Женский",
   sex==2~"Мужской"
  ),
  occupation_type=case_when(
   occupation_type=="work"~"Работа",
   occupation_type=="university"~"Высшее образование",
   occupation_type=="school"~"Среднее образование",
    )) %>% 
  rename(Пол=sex, Занятость=occupation_type, Возраст=age, Город=city_title, Страна=country_title)

Создаем таблицу в разрезе групп

Чтобы сделать общую таблицу, воспользуемся пакетом: gtsummary:

library(gtsummary)
table<-info %>%
  tbl_summary(missing="no", by=group) # missing="no" - мы не будем отображать пропущенные значения, by=group - будем делать анализ по группам
table
Characteristic ИГН, N = 3,3771 Кафедра, N = 2061 РНД, N = 6771
Возраст 21 (19, 24) 24 (20, 31) 37 (27, 48)
Пол


    Женский 2,584 (77%) 174 (84%) 518 (77%)
    Мужской 792 (23%) 32 (16%) 159 (23%)
Занятость


    Высшее образование 1,579 (69%) 103 (66%) 215 (54%)
    Работа 650 (28%) 50 (32%) 178 (44%)
    Среднее образование 63 (2.7%) 2 (1.3%) 8 (2.0%)
Страна


    Австралия 1 (<0.1%) 0 (0%) 0 (0%)
    Австрия 1 (<0.1%) 0 (0%) 1 (0.2%)
    Алжир 1 (<0.1%) 0 (0%) 0 (0%)
    Ангилья 1 (<0.1%) 1 (0.6%) 0 (0%)
    Беларусь 2 (<0.1%) 0 (0%) 0 (0%)
    Великобритания 2 (<0.1%) 0 (0%) 0 (0%)
    Венесуэла 1 (<0.1%) 0 (0%) 0 (0%)
    Германия 6 (0.3%) 0 (0%) 19 (3.4%)
    Гондурас 1 (<0.1%) 0 (0%) 0 (0%)
    Грузия 2 (<0.1%) 0 (0%) 0 (0%)
    Израиль 1 (<0.1%) 0 (0%) 0 (0%)
    Испания 2 (<0.1%) 0 (0%) 0 (0%)
    Италия 1 (<0.1%) 0 (0%) 1 (0.2%)
    Казахстан 69 (3.0%) 2 (1.2%) 3 (0.5%)
    Канада 1 (<0.1%) 0 (0%) 0 (0%)
    Китай 5 (0.2%) 0 (0%) 1 (0.2%)
    Кыргызстан 3 (0.1%) 2 (1.2%) 1 (0.2%)
    Латвия 2 (<0.1%) 0 (0%) 0 (0%)
    Микронезия, федеративные штаты 1 (<0.1%) 0 (0%) 0 (0%)
    Объединенные Арабские Эмираты 1 (<0.1%) 0 (0%) 0 (0%)
    Польша 1 (<0.1%) 0 (0%) 1 (0.2%)
    Россия 2,185 (94%) 153 (95%) 526 (94%)
    Словения 1 (<0.1%) 0 (0%) 0 (0%)
    США 15 (0.6%) 0 (0%) 0 (0%)
    Таджикистан 3 (0.1%) 0 (0%) 0 (0%)
    Танзания 0 (0%) 1 (0.6%) 0 (0%)
    Турция 0 (0%) 1 (0.6%) 0 (0%)
    Узбекистан 3 (0.1%) 0 (0%) 0 (0%)
    Украина 4 (0.2%) 1 (0.6%) 2 (0.4%)
    Франция 3 (0.1%) 0 (0%) 0 (0%)
    Швейцария 0 (0%) 0 (0%) 2 (0.4%)
    Швеция 1 (<0.1%) 0 (0%) 0 (0%)
    Южная Корея 2 (<0.1%) 0 (0%) 0 (0%)
    Япония 11 (0.5%) 0 (0%) 0 (0%)
Город


    Abu Dhabi 1 (<0.1%) 0 (0%) 0 (0%)
    Albuquerque 1 (<0.1%) 0 (0%) 0 (0%)
    Atlanta 1 (<0.1%) 0 (0%) 0 (0%)
    Barkow 1 (<0.1%) 0 (0%) 0 (0%)
    Batman 0 (0%) 1 (0.8%) 0 (0%)
    Bebra 2 (0.1%) 0 (0%) 0 (0%)
    Beijing 1 (<0.1%) 0 (0%) 0 (0%)
    Berlin 0 (0%) 0 (0%) 6 (1.1%)
    Bielefeld 0 (0%) 0 (0%) 1 (0.2%)
    Boston 1 (<0.1%) 0 (0%) 0 (0%)
    Busan 1 (<0.1%) 0 (0%) 0 (0%)
    California 1 (<0.1%) 0 (0%) 0 (0%)
    Capri 1 (<0.1%) 0 (0%) 0 (0%)
    Chiba 1 (<0.1%) 0 (0%) 0 (0%)
    Dortmund 0 (0%) 0 (0%) 1 (0.2%)
    Dresden 0 (0%) 0 (0%) 1 (0.2%)
    Florida City 1 (<0.1%) 0 (0%) 0 (0%)
    Frankfurt am Main 0 (0%) 0 (0%) 1 (0.2%)
    Hamburg 0 (0%) 0 (0%) 1 (0.2%)
    Hannover 0 (0%) 0 (0%) 1 (0.2%)
    Hattingen 0 (0%) 0 (0%) 1 (0.2%)
    Honolulu 1 (<0.1%) 0 (0%) 0 (0%)
    Kanavayén 1 (<0.1%) 0 (0%) 0 (0%)
    Kawasaki 1 (<0.1%) 0 (0%) 0 (0%)
    Kyōto 1 (<0.1%) 0 (0%) 0 (0%)
    Lanzhou 1 (<0.1%) 0 (0%) 0 (0%)
    Lenzburg 0 (0%) 0 (0%) 1 (0.2%)
    Lichtenfels 0 (0%) 0 (0%) 1 (0.2%)
    Linz 1 (<0.1%) 0 (0%) 0 (0%)
    Liverpool 1 (<0.1%) 0 (0%) 0 (0%)
    London 1 (<0.1%) 0 (0%) 0 (0%)
    Lörrach 0 (0%) 0 (0%) 1 (0.2%)
    Los Angeles 2 (0.1%) 0 (0%) 0 (0%)
    Lübeck 0 (0%) 0 (0%) 1 (0.2%)
    Madrid 1 (<0.1%) 0 (0%) 0 (0%)
    Malibu 1 (<0.1%) 0 (0%) 0 (0%)
    Moscow 1 (<0.1%) 0 (0%) 0 (0%)
    München 1 (<0.1%) 0 (0%) 0 (0%)
    New York City 1 (<0.1%) 0 (0%) 0 (0%)
    Nitzana 1 (<0.1%) 0 (0%) 0 (0%)
    Ōsaka 1 (<0.1%) 0 (0%) 0 (0%)
    Palermo 1 (<0.1%) 0 (0%) 0 (0%)
    Paris 1 (<0.1%) 0 (0%) 0 (0%)
    Philadelphia 1 (<0.1%) 0 (0%) 0 (0%)
    Portorož 1 (<0.1%) 0 (0%) 0 (0%)
    Québec 1 (<0.1%) 0 (0%) 0 (0%)
    Salzburg 0 (0%) 0 (0%) 1 (0.2%)
    Sanya 1 (<0.1%) 0 (0%) 0 (0%)
    Seoul 1 (<0.1%) 0 (0%) 0 (0%)
    Siegburg 0 (0%) 0 (0%) 1 (0.2%)
    Stockholm 1 (<0.1%) 0 (0%) 0 (0%)
    Taiyuan 1 (<0.1%) 0 (0%) 0 (0%)
    Tokyo 3 (0.2%) 0 (0%) 0 (0%)
    Weiden in der Oberpfalz 0 (0%) 0 (0%) 1 (0.2%)
    Witten 0 (0%) 0 (0%) 1 (0.2%)
    Wrocław 1 (<0.1%) 0 (0%) 0 (0%)
    Xingtai 1 (<0.1%) 0 (0%) 0 (0%)
    Yamatotakada 1 (<0.1%) 0 (0%) 0 (0%)
    Yokohama 1 (<0.1%) 0 (0%) 0 (0%)
    Zug 0 (0%) 0 (0%) 1 (0.2%)
    Абакан 3 (0.2%) 1 (0.8%) 0 (0%)
    Алейск 4 (0.2%) 0 (0%) 1 (0.2%)
    Алейский 1 (<0.1%) 0 (0%) 0 (0%)
    Александровское 1 (<0.1%) 0 (0%) 0 (0%)
    Алматы 6 (0.3%) 0 (0%) 0 (0%)
    Алтайское 2 (0.1%) 0 (0%) 0 (0%)
    Анжеро-Судженск 2 (0.1%) 1 (0.8%) 0 (0%)
    Астана 2 (0.1%) 0 (0%) 0 (0%)
    Ачинск 1 (<0.1%) 0 (0%) 0 (0%)
    Баево 1 (<0.1%) 0 (0%) 0 (0%)
    Балашиха 1 (<0.1%) 0 (0%) 0 (0%)
    Барнаул 1,359 (72%) 89 (67%) 314 (60%)
    Барнаул (деревня) 3 (0.2%) 0 (0%) 4 (0.8%)
    Белгород 0 (0%) 1 (0.8%) 0 (0%)
    Белокуриха 4 (0.2%) 0 (0%) 1 (0.2%)
    Белояровка 1 (<0.1%) 0 (0%) 0 (0%)
    Бердск 1 (<0.1%) 0 (0%) 0 (0%)
    Бийск 31 (1.6%) 5 (3.8%) 6 (1.1%)
    Бишкек 3 (0.2%) 2 (1.5%) 1 (0.2%)
    Борзя 1 (<0.1%) 0 (0%) 0 (0%)
    Борисов 1 (<0.1%) 0 (0%) 0 (0%)
    Братск 2 (0.1%) 0 (0%) 0 (0%)
    Брянск 0 (0%) 0 (0%) 1 (0.2%)
    Бурла 2 (0.1%) 0 (0%) 1 (0.2%)
    Валдай 0 (0%) 0 (0%) 1 (0.2%)
    Верх-Уймон 0 (0%) 0 (0%) 2 (0.4%)
    Витим 1 (<0.1%) 0 (0%) 0 (0%)
    Вичуга 0 (0%) 1 (0.8%) 0 (0%)
    Владимир 1 (<0.1%) 0 (0%) 1 (0.2%)
    Вологда 0 (0%) 0 (0%) 1 (0.2%)
    Волосово 1 (<0.1%) 0 (0%) 0 (0%)
    Воронеж 1 (<0.1%) 0 (0%) 1 (0.2%)
    Вострово 0 (0%) 0 (0%) 1 (0.2%)
    Воткинск 1 (<0.1%) 0 (0%) 0 (0%)
    Выдриха 1 (<0.1%) 1 (0.8%) 0 (0%)
    Горно-Алтайск 18 (0.9%) 0 (0%) 3 (0.6%)
    Горняк 2 (0.1%) 0 (0%) 0 (0%)
    Гурзуф 1 (<0.1%) 0 (0%) 0 (0%)
    Дербент 0 (0%) 0 (0%) 1 (0.2%)
    Дмитриевка 1 (<0.1%) 0 (0%) 0 (0%)
    Днепр (Днепропетровск) 1 (<0.1%) 0 (0%) 0 (0%)
    Донецк 1 (<0.1%) 0 (0%) 0 (0%)
    Дудинка 1 (<0.1%) 0 (0%) 0 (0%)
    Душанбе 1 (<0.1%) 0 (0%) 0 (0%)
    Екатеринбург 4 (0.2%) 0 (0%) 1 (0.2%)
    Енисейск 1 (<0.1%) 0 (0%) 0 (0%)
    Енисейское 1 (<0.1%) 0 (0%) 0 (0%)
    Завьялово 4 (0.2%) 1 (0.8%) 0 (0%)
    Закаменск 0 (0%) 0 (0%) 1 (0.2%)
    Заринск 12 (0.6%) 1 (0.8%) 2 (0.4%)
    Зеленоградск 0 (0%) 0 (0%) 1 (0.2%)
    Змеиногорск 1 (<0.1%) 1 (0.8%) 3 (0.6%)
    Зудилово 3 (0.2%) 0 (0%) 0 (0%)
    Зура 0 (0%) 0 (0%) 1 (0.2%)
    Игра 0 (0%) 0 (0%) 1 (0.2%)
    Иркутск 1 (<0.1%) 0 (0%) 1 (0.2%)
    Искитим 1 (<0.1%) 0 (0%) 0 (0%)
    Истиклол (Табошар) 1 (<0.1%) 0 (0%) 0 (0%)
    Ичня 0 (0%) 0 (0%) 1 (0.2%)
    Казань 2 (0.1%) 0 (0%) 1 (0.2%)
    Калининград 2 (0.1%) 0 (0%) 2 (0.4%)
    Калуга 1 (<0.1%) 0 (0%) 0 (0%)
    Камень-на-Оби 6 (0.3%) 0 (0%) 0 (0%)
    Канск 0 (0%) 0 (0%) 1 (0.2%)
    Караганда 1 (<0.1%) 0 (0%) 0 (0%)
    Касансай 1 (<0.1%) 0 (0%) 0 (0%)
    Кемерово 2 (0.1%) 0 (0%) 0 (0%)
    Киров 1 (<0.1%) 0 (0%) 0 (0%)
    Кладовка 1 (<0.1%) 0 (0%) 0 (0%)
    Комсомольск-на-Амуре 0 (0%) 0 (0%) 1 (0.2%)
    Кострома 1 (<0.1%) 0 (0%) 0 (0%)
    Кош-Агач 1 (<0.1%) 0 (0%) 0 (0%)
    Краснодар 2 (0.1%) 0 (0%) 0 (0%)
    Красноярск 2 (0.1%) 0 (0%) 1 (0.2%)
    Красный Партизан 0 (0%) 0 (0%) 1 (0.2%)
    Кубанка 0 (0%) 0 (0%) 1 (0.2%)
    Кулунда 3 (0.2%) 0 (0%) 0 (0%)
    Курган 0 (0%) 0 (0%) 1 (0.2%)
    Куюс 0 (0%) 0 (0%) 1 (0.2%)
    Кызыл 4 (0.2%) 2 (1.5%) 0 (0%)
    Кытманово 2 (0.1%) 0 (0%) 0 (0%)
    Ленинск-Кузнецкий 1 (<0.1%) 0 (0%) 0 (0%)
    Липецк 1 (<0.1%) 0 (0%) 0 (0%)
    Магадан 1 (<0.1%) 0 (0%) 0 (0%)
    Магнитогорск 0 (0%) 0 (0%) 1 (0.2%)
    Мамонтово 1 (<0.1%) 1 (0.8%) 3 (0.6%)
    Манжерок 0 (0%) 0 (0%) 1 (0.2%)
    Маслянино 1 (<0.1%) 0 (0%) 0 (0%)
    Междуреченск 3 (0.2%) 0 (0%) 0 (0%)
    Мир 1 (<0.1%) 0 (0%) 0 (0%)
    Могилёв 1 (<0.1%) 0 (0%) 0 (0%)
    Москва 39 (2.1%) 5 (3.8%) 19 (3.6%)
    Набережные Челны 0 (0%) 0 (0%) 1 (0.2%)
    Надым 1 (<0.1%) 0 (0%) 0 (0%)
    Нижнеангарск 1 (<0.1%) 0 (0%) 0 (0%)
    Нижневартовск 2 (0.1%) 0 (0%) 0 (0%)
    Николаев 1 (<0.1%) 0 (0%) 0 (0%)
    Новичиха 0 (0%) 0 (0%) 1 (0.2%)
    Новоалтайск 22 (1.2%) 4 (3.0%) 16 (3.1%)
    Новодвинск 0 (0%) 0 (0%) 2 (0.4%)
    Новоегорьевское 1 (<0.1%) 0 (0%) 0 (0%)
    Новокузнецк 5 (0.3%) 0 (0%) 1 (0.2%)
    Новомосковск 0 (0%) 0 (0%) 1 (0.2%)
    Новоперуново 1 (<0.1%) 0 (0%) 0 (0%)
    Новороссийск 1 (<0.1%) 0 (0%) 0 (0%)
    Новосибирск 43 (2.3%) 1 (0.8%) 20 (3.8%)
    Новоуткинск 0 (0%) 0 (0%) 1 (0.2%)
    Норильск 0 (0%) 0 (0%) 1 (0.2%)
    Омск 3 (0.2%) 0 (0%) 4 (0.8%)
    Орлеан 0 (0%) 0 (0%) 1 (0.2%)
    Отрадное 0 (0%) 0 (0%) 1 (0.2%)
    Павловск 4 (0.2%) 1 (0.8%) 1 (0.2%)
    Павловский Посад 1 (<0.1%) 0 (0%) 0 (0%)
    Павлодар 10 (0.5%) 1 (0.8%) 0 (0%)
    Партизанск 1 (<0.1%) 0 (0%) 0 (0%)
    Пенза 1 (<0.1%) 0 (0%) 0 (0%)
    Пермь 4 (0.2%) 0 (0%) 1 (0.2%)
    Петрозаводск 1 (<0.1%) 0 (0%) 1 (0.2%)
    Петропавловское 0 (0%) 0 (0%) 1 (0.2%)
    Победим 1 (<0.1%) 0 (0%) 0 (0%)
    Подольск 0 (0%) 0 (0%) 1 (0.2%)
    Подсосново 0 (0%) 0 (0%) 1 (0.2%)
    Покровка 0 (0%) 1 (0.8%) 0 (0%)
    Полковниково 0 (0%) 0 (0%) 1 (0.2%)
    Полысаево 1 (<0.1%) 0 (0%) 0 (0%)
    Порожнее 1 (<0.1%) 0 (0%) 0 (0%)
    Поспелиха 2 (0.1%) 0 (0%) 0 (0%)
    Поспелихинский 1 (<0.1%) 0 (0%) 0 (0%)
    Прокопьевск 4 (0.2%) 0 (0%) 0 (0%)
    Простоквашино 1 (<0.1%) 0 (0%) 0 (0%)
    Прутской 1 (<0.1%) 0 (0%) 0 (0%)
    Пушкино 1 (<0.1%) 0 (0%) 0 (0%)
    Развилка 1 (<0.1%) 0 (0%) 0 (0%)
    Рассыпное 1 (<0.1%) 0 (0%) 0 (0%)
    Ребриха 1 (<0.1%) 0 (0%) 0 (0%)
    Рига 2 (0.1%) 0 (0%) 0 (0%)
    Риддер 7 (0.4%) 0 (0%) 2 (0.4%)
    Родино 2 (0.1%) 0 (0%) 0 (0%)
    Романово 0 (0%) 0 (0%) 1 (0.2%)
    Ростов-на-Дону 0 (0%) 0 (0%) 1 (0.2%)
    Рубцовск 22 (1.2%) 2 (1.5%) 2 (0.4%)
    Рязань 1 (<0.1%) 0 (0%) 0 (0%)
    Самара 2 (0.1%) 0 (0%) 2 (0.4%)
    Санкт-Петербург 19 (1.0%) 2 (1.5%) 16 (3.1%)
    Севастополь 2 (0.1%) 0 (0%) 0 (0%)
    Северск 1 (<0.1%) 0 (0%) 0 (0%)
    Семей 15 (0.8%) 0 (0%) 0 (0%)
    Сибирский 1 (<0.1%) 0 (0%) 1 (0.2%)
    Симферополь 1 (<0.1%) 0 (0%) 0 (0%)
    Славгород 8 (0.4%) 1 (0.8%) 3 (0.6%)
    Смоленск 0 (0%) 0 (0%) 1 (0.2%)
    Советское 1 (<0.1%) 0 (0%) 0 (0%)
    Сочи 1 (<0.1%) 0 (0%) 2 (0.4%)
    Ставрополь 0 (0%) 0 (0%) 1 (0.2%)
    Староалейское 1 (<0.1%) 0 (0%) 0 (0%)
    Старобелокуриха 1 (<0.1%) 0 (0%) 0 (0%)
    Степное Озеро 1 (<0.1%) 0 (0%) 0 (0%)
    Сузун 1 (<0.1%) 0 (0%) 0 (0%)
    Сулея 1 (<0.1%) 0 (0%) 0 (0%)
    Сургут 4 (0.2%) 0 (0%) 1 (0.2%)
    Сухая Чемровка 1 (<0.1%) 0 (0%) 0 (0%)
    Талгар 1 (<0.1%) 0 (0%) 0 (0%)
    Тальменка 4 (0.2%) 1 (0.8%) 0 (0%)
    Ташкент 2 (0.1%) 0 (0%) 0 (0%)
    Тбилиси 2 (0.1%) 0 (0%) 0 (0%)
    Тверь 1 (<0.1%) 0 (0%) 0 (0%)
    Темиртау 1 (<0.1%) 0 (0%) 0 (0%)
    Тобольск 1 (<0.1%) 0 (0%) 0 (0%)
    Томск 9 (0.5%) 0 (0%) 3 (0.6%)
    Топольное 0 (0%) 0 (0%) 1 (0.2%)
    Топчиха 1 (<0.1%) 0 (0%) 1 (0.2%)
    Троицк 2 (0.1%) 0 (0%) 0 (0%)
    Троицкое 3 (0.2%) 1 (0.8%) 1 (0.2%)
    Турочак 0 (0%) 0 (0%) 1 (0.2%)
    Тюмень 3 (0.2%) 0 (0%) 1 (0.2%)
    Тяжинский 1 (<0.1%) 0 (0%) 0 (0%)
    Улан-Удэ 1 (<0.1%) 0 (0%) 0 (0%)
    Ульяновск 0 (0%) 0 (0%) 1 (0.2%)
    Усть-Илимск 1 (<0.1%) 0 (0%) 0 (0%)
    Усть-Каменогорск 1 (<0.1%) 0 (0%) 0 (0%)
    Усть-Кокса 0 (0%) 0 (0%) 1 (0.2%)
    Усть-Мосиха 0 (0%) 0 (0%) 1 (0.2%)
    Усть-Таловка 1 (<0.1%) 0 (0%) 0 (0%)
    Усть-Чарышская Пристань 1 (<0.1%) 0 (0%) 0 (0%)
    Уфа 2 (0.1%) 1 (0.8%) 0 (0%)
    Ухта 0 (0%) 0 (0%) 1 (0.2%)
    Уштобе 1 (<0.1%) 0 (0%) 0 (0%)
    Хабаровск 1 (<0.1%) 0 (0%) 1 (0.2%)
    Ханты-Мансийск 1 (<0.1%) 0 (0%) 1 (0.2%)
    Харьков 0 (0%) 1 (0.8%) 0 (0%)
    Хреново 1 (<0.1%) 0 (0%) 0 (0%)
    Худжанд 1 (<0.1%) 0 (0%) 0 (0%)
    Чарышское 0 (0%) 0 (0%) 1 (0.2%)
    Чебоксары 0 (0%) 0 (0%) 1 (0.2%)
    Челябинск 1 (<0.1%) 1 (0.8%) 1 (0.2%)
    Черепаново 1 (<0.1%) 0 (0%) 0 (0%)
    Черногорск 2 (0.1%) 0 (0%) 0 (0%)
    Чистоозерка 0 (0%) 0 (0%) 1 (0.2%)
    Чоя 1 (<0.1%) 0 (0%) 0 (0%)
    Шахты 1 (<0.1%) 0 (0%) 0 (0%)
    Шебалино 1 (<0.1%) 0 (0%) 1 (0.2%)
    Шемонаиха 3 (0.2%) 0 (0%) 0 (0%)
    Шипуново 0 (0%) 0 (0%) 3 (0.6%)
    Шушенское 1 (<0.1%) 0 (0%) 1 (0.2%)
    Южно-Сахалинск 1 (<0.1%) 0 (0%) 0 (0%)
    Яконур 1 (<0.1%) 0 (0%) 0 (0%)
    Яровое 7 (0.4%) 1 (0.8%) 0 (0%)
    Ярославцев Лог 0 (0%) 0 (0%) 1 (0.2%)
1 Median (IQR); n (%)

Таблица получилась красивая, но слишком длинная. В завершении давайте «свернем» страны и города, оставив в качестве основной категории для страны - Россию, а для населенного пункта - Барнаул. Если у Вас группы по разным регионам (по Алтайскому краю, Республике Алтай), то лучше делать отдельный анализ по каждому региону.

library(forcats)
info<-info %>%
  mutate(
    Страна=fct_collapse(info$Страна,
   Россия="Россия",
   other_level = "Другие"
  ),
   Город=fct_collapse(Город,
   Барнаул="Барнаул",
   other_level = "Другие"
  ))

Сделаем окончательную таблицу:

table2<-info %>%
  tbl_summary(missing="no", by=group) %>% 
   modify_header(#функция, позволяющая изменить заголовок
    update = list(
      label ~ "Характеристика" # Заменим слово Characteristic
    )
  )
table2
Характеристика ИГН, N = 3,3771 Кафедра, N = 2061 РНД, N = 6771
Возраст 21 (19, 24) 24 (20, 31) 37 (27, 48)
Пол


    Женский 2,584 (77%) 174 (84%) 518 (77%)
    Мужской 792 (23%) 32 (16%) 159 (23%)
Занятость


    Высшее образование 1,579 (69%) 103 (66%) 215 (54%)
    Работа 650 (28%) 50 (32%) 178 (44%)
    Среднее образование 63 (2.7%) 2 (1.3%) 8 (2.0%)
Страна


    Россия 2,185 (94%) 153 (95%) 526 (94%)
    Другие 148 (6.3%) 8 (5.0%) 31 (5.6%)
Город


    Барнаул 1,359 (72%) 89 (67%) 314 (60%)
    Другие 538 (28%) 43 (33%) 208 (40%)
1 Median (IQR); n (%)

Самостоятельная работа

  1. Сделать сводную таблицу по своим группам
  2. Описать полученные результаты
  3. Кроме данных из таблицы добавить в описание другую интересную информацию, там, где данных достаточно (для этого создать отдельные таблички самостоятельно)