Лірика

Перш ніж перейти до розповіді про роботу з Twitter скажу, що незамінним помічником при роботі з пакетами в R є документації. У цьому випадку мова йде про цю документацію. Функції, написані там, по суті треба по-різному компонувати з іншими - майже вся суть роботи. Особливо корисним є застосування apply функцій та векторизації, що пришвидшують роботу коду. Тут це дуже важливо, бо дані викачуються не дуже швидко, є повно обмежень на викачку, на які потрібно зважати (про них можна почитати тут). Також потрібно постійно цікавитись іншими бібліотеками, які можуть виконувати цілу масу корисних функцій, що значно полегшують роботу. В R цих бібліотек більше 7 тисяч.

Підключення до Twitter

Спочатку через цe послання створюємо свій застосунок у твіттері, за допомогою якого будемо викачувати дані https://dev.twitter.com/ Детальніше почитати, як його створити можна тут.

Тепер інсталюємо та підключаємо потрібні пакети в R. Вони допомагають нам підключитися до твіттера

install.packages("twitteR")
install.packages("ROAuth")
install.packages("httr")
install.packages("ggplot2")
library("twitteR")
library("ROAuth")
library("streamR")
library(httr)

Запишемо всі ключі додатка (у кожного додатка вони свої)

CUSTOMER_KEY='FZQtyEPqNtKORjFNkuFYoqz8c'
CUSTOMER_SECRET='aQp7R0ssflOxt0SYuPtPbLFYmReB9AiW8bo8II3v6MYEThhm43'
ACCESS_TOKEN='316537972-jt3gaRWpWqNXvZZdAOomWLHNygPXoV60GXsQbK1v'
ACCESS_secret='avsz6vGEn2yOcwPzOaptmamiDNWA3f0AJjc7ZCjl47NZo'

Авторизуємо додаток в R

setup_twitter_oauth(CUSTOMER_KEY, CUSTOMER_SECRET, ACCESS_TOKEN, ACCESS_secret)
## [1] "Using direct authentication"

Тепер ми можемо викачувати дані з Twitter. Однак не безмежно. Існують певні обмеження на викачку даних у об’ємі за певний час.

Робота з інформацією про користувачів

Додати користувача за логіном

user <- getUser('lsv3069')

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

user <- as.data.frame(user)

Але зазвичай викачується ціла маса аккаунтів користувачів. Для цього застосовується інша функція, при цьому потрібні аккаунти потрібно загорнути в список.

users <- lookupUsers(list('lsv3069','rinacem'))

Або

users <- lapply(list('lsv3069','rinacem'),getUser)

Тест на швидкість

system.time(lookupUsers(list('lsv3069','rinacem')))
##    user  system elapsed 
##   0.201   0.273   1.695
system.time(lapply(list('lsv3069','rinacem'),getUser))
##    user  system elapsed 
##   0.198   0.131   1.993

Туторіал рекомендує використовувати першу функцію, але друга написана на C++, тому вона швидша в реалізації. І взагалі рекомендую часто використовувати apply функції, вони дуже потужні

У таблицю даних отриманий список переформатувати теж тепер не так просто

users.df <- do.call("rbind",lapply(users,as.data.frame))

r head(users.df) description

1 нищая выпускница гуманитарного колледжа, с нулевыми перспективами ( ͡° \u035cʖ ͡°) ͡° \u035cʖ ͡°)
2                                  каждый неправильный поворот ведет к единственно правильному.
  statusesCount followersCount favoritesCount friendsCount                     url           name
1          2184             58            575           42                    <NA>    Malvina .-.
2         11943            154           1442           65 https://t.co/kBUyPfyAVp Marina Smahina
              created protected verified screenName location lang         id listedCount
1 2015-04-19 07:54:17     FALSE    FALSE    lsv3069            ru 3182240613           0
2 2010-10-20 13:47:38     FALSE    FALSE    rinacem     Київ   ru  205265370           4
  followRequestSent                                                            profileImageUrl
1             FALSE http://pbs.twimg.com/profile_images/717154973642059776/EVGZcJHS_normal.jpg
2             FALSE http://pbs.twimg.com/profile_images/738996472679673856/vLkI6ZEu_normal.jpg

Всього в таблиці 17 змінних, однак ми можемо вирахувати ще додаткові. Наприклад, кількість днів, які минули з моменту реєстрації користувача в Twitter, а з цього і його активність.

Визначення статі

Окремо по кожній таблиці можна рахувати пропорції за статтю. Вони не мають ідеальної точності, адже спираються на аналіз імен

У цьому нам може допомогти бібліотека gender

library(gender)

Бібліотека stringi потрібна нам для того, щоб конвертувати все в латиницю (бо бібліотека gender працює саме на ній)

library(stringi)

Для застосування функції ми розбиваємо всі імена і прізвища на різні стовбці так, аби сформувався один вектор з окремими елементами. Всі імена і прізвища транслітеруємо у латиницю і робимо всі букви малим шрифтом. Прізвища зазвичай функція просто ігнорує. Результатом буде таблиця, в якій показуватиметься ким є за статтю користувач і яка при цьому ймовірність цього:

user.gender <- gender((tolower(stri_trans_general(unlist(strsplit(users.df$name, " ")), "cyrillic-latin; nfd; [:nonspacing mark:] remove; nfc"))))
user.gender
## Source: local data frame [2 x 6]
## 
##      name proportion_male proportion_female gender year_min year_max
##     (chr)           (dbl)             (dbl)  (chr)    (dbl)    (dbl)
## 1 malvina          0.0000            1.0000 female     1932     2012
## 2  marina          0.0015            0.9985 female     1932     2012

Зробимо таблицю розподілу за статтю

sex.user <- data.frame(table(user.gender$gender))
sex.user$percent<-sex.user$Freq/sum(sex.user$Freq)*100
sex.user
##     Var1 Freq percent
## 1 female    2     100

У випадку двох аккаунтів це не має особливого сенсу, але коли мова йде, наприклад, про всіх користувачів, які підписані на якогось лідера думок, то є цікавим глянути, хто переважає за статтю серед його підписників

Коли аккаунтів для аналізу багато, то життя можна дещо спростити, застосувавши ці прості функції:

list.gend <- function(x){
  x.gender <- gender((tolower(stri_trans_general(unlist(strsplit(x$name, " ")), "cyrillic-latin; nfd; [:nonspacing mark:] remove; nfc"))))
  sex.x <- data.frame(table(x.gender$gender))
  sex.x$percent <- sex.x$Freq/sum(sex.x$Freq)*100
  sex.x
}

Вони роблять точно те саме, що і вище. У комбінації з lapply вони дозволять швидко проаналізувати великий масив даних на статевий розподіл

Спрощуємо життя

Тепер спробуємо отримати список підписників на аккаунт. Для цього обов’язково потрібно мати вище згаданий список

Викачати дані про одного користувача

user.followers <- users[[1]]$getFollowers()

Викачати дані про друзів багатотьох користувачів можна через функцію

list.followers <- function(x){
  z <- do.call("rbind",lapply(x$getFollowers(),as.data.frame))
  return (z)
}

Так ми отримаємо список із списків підписників на аккаунт

users.followers <- lapply(users,list.followers)

Для підписок все виглядає абсолютно аналогічно

list.friends <- function(x){
  z <- do.call("rbind",lapply(x$getFriends(),as.data.frame))
  return (z)
}

Так ми отримаємо список із списків підписок аккаунту

users.friends <- lapply(users,list.friends)

Формат списку не дуже зручний для роботи. І його не переведеш у формат SPSS, тому існує необхідність у конвертуванні цих списків у таблиці данних. При цьому можна робити окремі таблиці даних для кожного користувача, а можна одну-єдину з відповідними мітками користувачів Зазвичай по рядах “склеїти” таблицю даних допомагає функція rbind, але нам потрібні ще і мітки для розрізнення. Їх можна проставити за допомогою функції combine з бібліотеки gdata (source - мітка)

library(gdata)

Також варто проставити імена для кожного списку (це ледь не єдина перевага використання lookupUsers перед lapply + getUser: перша функція ці імена вставляє, а друга - ні)

names(users.followers) <- c("user1","user2")

Ось тепер ми отримуємо єдину таблицю даних з підписників двох аккаунтів

users.followers.df <- do.call("combine", lapply(users.followers,as.data.frame))

Повторимо цю процедуру для підписок

names(users.friends) <- c("user1","user2")
users.friends.df <- do.call("combine", lapply(users.friends,as.data.frame))

Тепер можемо все (і підписки, і підписників) “склеїти” в одну цілу таблицю

users.table <- combine(users.friends.df,users.followers.df)

Можемо доповнити масив деякою інформацією

#чи є своє зображення у профілю
users.table$image <- ifelse(substring(users.table$profileImageUrl,1,10)=="http://pbs",1,0)
#чи є інформація про локацію
users.table$local <- ifelse(users.table$location!="",1,0)
#чи є посилання на інші аккаунти, сайти у профілі
users.table$adres <- ifelse(is.na(users.table$url),0,1)
#скільки днів існує профіль
users.table$days <- as.numeric(as.Date(Sys.time())-as.Date(users.table$created))
#скільки повідомлень в день генерує користувач в середньому
users.table$statusdays <- round(users.table$statusesCount/users.table$days,2)

Цю таблицю можемо зберегти у форматі .sav для SPSS

library(sjmisc)
## 
## Attaching package: 'sjmisc'
## The following object is masked from 'package:gdata':
## 
##     trim
## The following object is masked from 'package:psych':
## 
##     phi
write_spss(users.table, "users.table.sav", enc.to.utf8 = TRUE)

Можемо тепер зробити гендерні розподіли Користувачі, яких читає користувач 1

list.gend(users.table[users.table$source=="user1" & users.table$source.1=="users.friends.df",])
##     Var1 Freq  percent
## 1 female    5 55.55556
## 2   male    4 44.44444

Користувачі, яких читає користувач 2

list.gend(users.table[users.table$source=="user2" & users.table$source.1=="users.friends.df",])
##     Var1 Freq  percent
## 1 female   30 61.22449
## 2   male   19 38.77551

Користувачі, які читають користувача 1

list.gend(users.table[users.table$source=="user1" & users.table$source.1=="users.followers.df",])
##     Var1 Freq  percent
## 1 female   13 59.09091
## 2   male    9 40.90909

Користувачі, які читають користувача 2

list.gend(users.table[users.table$source=="user2" & users.table$source.1=="users.followers.df",])
##     Var1 Freq  percent
## 1 female   51 62.96296
## 2   male   30 37.03704

Відсіюємо ботів

Досить часто, особливо у популярних аккаунтів, підписниками є боти. Їх не завжди точно можна визначити, але в переважній більшості випадків вони малоактивні і мають диспропорцію підписок і підписників. Найчастіше я використовував відсів аккаунтів, де число статусів менше 5, а число підписок в 10 разів більше, ніж підписників. Тестування моделей за допомогою технології machine learning взагалі показало, що оптимальна модель - відсіяти просто тих, у кого число статусів менше 5. Це дозволить з точністю 0.85 отримати “живі” аккаунти в масиві.

kor.users.table <- users.table[(users.table$followersCount/users.table$friendsCount)>0.1 & users.table$statusesCount>5, ]

З масиву було видалено 33 аккаунти, які не відповідали цим критеріям. Цю процедуру можна і потрібно покращувати. Цьому я ще присвячу окрему статтю

Що можна ще зробити з отриманими даними?

Можна перевірити число спільних підписок і підписників в обох аккаунтів Спільні підписки:

a <- list(kor.users.table[1:23,11],kor.users.table[24:87,11])
names(a) <- c("user1","user2") 
cros.df <- crossprod(table(stack(a)))
cros.df
##        ind
## ind     user1 user2
##   user1    23     0
##   user2     0    64

Спільні підписники:

b <- list(kor.users.table[88:137,11],kor.users.table[138:157,11])
names(b) <- c("user1","user2") 
cros2.df <- crossprod(table(stack(b)))
cros2.df
##        ind
## ind     user1 user2
##   user1    50     0
##   user2     0    20

Як бачимо, мі цими двома користувачами нічого спільного. Але коли масив є більшим і збіги присутні, то ця таблиця може виступити основою для побудови графів. Схема побудови цих графів на прикладі даних з Вконтакті уже описана тут

Це далеко не всі можливості роботи з даними Twitter. Крім зв’язків підписок можна вивчати ще й інтенсивність і зміст комунікації між користувачами. Про це мова піде в наступній статті.