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