Saya menggunakan dataset twitter yang berisikan tweet bertopik NFT. Analisis ini bertujuan untuk memahami bagaimana interaksi akun-akun yang mengisi topik tersebut, berdasarkan aktivitas mention.
library(tidyverse)
# graph
library(tidygraph)
library(ggraph)
library(igraph)tweets <- read_rds("nft.RDS")
head(tweets, 10)tail(tweets, 20)glimpse(tweets)#> Rows: 17,681
#> Columns: 90
#> $ user_id <chr> "1482989135279554561", "1465321321018101774", ~
#> $ status_id <chr> "1484077026751705091", "1484077026546008068", ~
#> $ created_at <dttm> 2022-01-20 08:15:10, 2022-01-20 08:15:10, 202~
#> $ screen_name <chr> "MdMurad46986696", "artur_litau", "uzayhayat17~
#> $ text <chr> "<U+0001F6F8>There are different crystals on the earth. ~
#> $ source <chr> "Twitter Web App", "Twitter for iPhone", "Twit~
#> $ display_text_width <dbl> 140, 13, 4, 69, 140, 140, 140, 140, 139, 140, ~
#> $ reply_to_status_id <chr> NA, "1484076661943513088", "148407336790914663~
#> $ reply_to_user_id <chr> NA, "1469819305", "1728743684", NA, NA, NA, NA~
#> $ reply_to_screen_name <chr> NA, "drmctchr333", "misscryptolog", NA, NA, NA~
#> $ is_quote <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALS~
#> $ is_retweet <lgl> TRUE, FALSE, FALSE, TRUE, TRUE, TRUE, TRUE, TR~
#> $ favorite_count <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0~
#> $ retweet_count <int> 4105, 0, 0, 1305, 822, 264, 13, 1, 344, 1942, ~
#> $ quote_count <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ reply_count <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ hashtags <list> NA, NA, NA, NA, NA, <"NFTs", "HODL", "gems", ~
#> $ symbols <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "REVO~
#> $ urls_url <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ urls_t.co <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ urls_expanded_url <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ media_url <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ media_t.co <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ media_expanded_url <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ media_type <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ ext_media_url <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ ext_media_t.co <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ ext_media_expanded_url <list> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ ext_media_type <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ mentions_user_id <list> "717023423696871424", <"1469819305", "1441155~
#> $ mentions_screen_name <list> "SpaceDAOBSC", <"drmctchr333", "BabyGhosts_NF~
#> $ lang <chr> "en", "en", "en", "en", "en", "en", "en", "en"~
#> $ quoted_status_id <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_text <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_created_at <dttm> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
#> $ quoted_source <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_favorite_count <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_retweet_count <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_user_id <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_screen_name <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_name <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_followers_count <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_friends_count <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_statuses_count <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_location <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_description <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ quoted_verified <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ retweet_status_id <chr> "1483999732905111552", NA, NA, "14840733679091~
#> $ retweet_text <chr> "<U+0001F6F8>There are different crystals on the earth. ~
#> $ retweet_created_at <dttm> 2022-01-20 03:08:02, NA, NA, 2022-01-20 08:00~
#> $ retweet_source <chr> "Twitter Web App", NA, NA, "Twitter for iPhone~
#> $ retweet_favorite_count <int> 4046, NA, NA, 369, 813, 272, 19, 1, 377, 1173,~
#> $ retweet_retweet_count <int> 4105, NA, NA, 1305, 822, 264, 13, 1, 344, 1942~
#> $ retweet_user_id <chr> "717023423696871424", NA, NA, "1728743684", "3~
#> $ retweet_screen_name <chr> "SpaceDAOBSC", NA, NA, "misscryptolog", "Brecc~
#> $ retweet_name <chr> "SpaceDAO", NA, NA, "Miss Cryptolog", "Brecci"~
#> $ retweet_followers_count <int> 51808, NA, NA, 557859, 187762, 124776, 341, 78~
#> $ retweet_friends_count <int> 128, NA, NA, 281, 846, 235, 2, 3149, 166, 11, ~
#> $ retweet_statuses_count <int> 48, NA, NA, 13019, 10258, 2349, 71, 259, 122, ~
#> $ retweet_location <chr> "Metaverse", NA, NA, "", "DM for PAID promo <U+0001F525>~
#> $ retweet_description <chr> "SpaceDAO innovation on GameFi world!\nTelegra~
#> $ retweet_verified <lgl> FALSE, NA, NA, FALSE, TRUE, FALSE, FALSE, FALS~
#> $ place_url <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ place_name <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ place_full_name <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ place_type <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ country <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ country_code <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ geo_coords <list> <NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>, <NA, ~
#> $ coords_coords <list> <NA, NA>, <NA, NA>, <NA, NA>, <NA, NA>, <NA, ~
#> $ bbox_coords <list> <NA, NA, NA, NA, NA, NA, NA, NA>, <NA, NA, NA~
#> $ status_url <chr> "https://twitter.com/MdMurad46986696/status/14~
#> $ name <chr> "Md Murad", "Artur Litau", "Uzay Hayat", "Uzay~
#> $ location <chr> "", "", "", "", "", "", "", "", "", "", "Lagos~
#> $ description <chr> "", "", "#SUPERFAMILY\n@LaCryptoMonkey\n#RichQ~
#> $ url <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "h~
#> $ protected <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALS~
#> $ followers_count <int> 2, 4, 302, 302, 96, 96, 299, 299, 6, 6, 43, 52~
#> $ friends_count <int> 55, 34, 4163, 4163, 4838, 4838, 732, 732, 1357~
#> $ listed_count <int> 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 7, 7, 0, 0, 0~
#> $ statuses_count <int> 166, 44, 21653, 21653, 50394, 50394, 2316, 231~
#> $ favourites_count <int> 78, 76, 17321, 17321, 9710, 9710, 2076, 2076, ~
#> $ account_created_at <dttm> 2022-01-17 08:12:36, 2021-11-29 14:07:40, 202~
#> $ verified <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALS~
#> $ profile_url <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "h~
#> $ profile_expanded_url <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "h~
#> $ account_lang <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ profile_banner_url <chr> NA, NA, "https://pbs.twimg.com/profile_banners~
#> $ profile_background_url <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
#> $ profile_image_url <chr> "http://pbs.twimg.com/profile_images/148298921~
tweets %>%
count(is_retweet)tweets %>%
filter(is_retweet) %>%
select(retweet_count) %>%
summary()#> retweet_count
#> Min. : 0
#> 1st Qu.: 88
#> Median : 458
#> Mean : 1379
#> 3rd Qu.: 1395
#> Max. :49552
tweets %>%
filter(retweet_count == 49552) %>%
select(screen_name,user_id, text)User dengan screen name b1nary_eth merupakan akun dengan jumlah retweet terbanyak pada dataset. adapun isi tweet tersebut dapat kita pahami sebagai pertanyaan sekaligus pertanyaan tentang in-game-currency yang menggunakan NFT. Bagaiamana kemudian berbagai game-NFT dapat bertahan, karena banyak entitas serupa justru tidak mampu bertahan karena kebijakan in game currency nya sendiri (menggunakan NFT/kripto).
(lihat referensi di sini)
cleansing <- tweets %>%
select(screen_name, mentions_screen_name) %>%
mutate(mentions_screen_name = as.character(mentions_screen_name))
head(cleansing, 100)mentions_screen_name menggunakan regular expression–> sekaligus menyiapkan objek edge_df
edge_df <-
cleansing %>%
select(screen_name, mentions_screen_name) %>%
mutate(mentions_screen_name = str_replace_all(string = mentions_screen_name,
pattern = "^c\\(|\\)$",
replacement = "")) %>%
separate_rows(mentions_screen_name, sep = ",") %>%
na.omit() %>%
mutate(mentions_screen_name = str_replace_all(string = mentions_screen_name,
pattern = "[[:punct]]+",
replacement = "")) %>%
rename(from = screen_name,
to = mentions_screen_name)
head(edge_df, 100)nodes_df (kumpulan screen_name pada edge_df)nodes_df <- data.frame(name = unique(c(edge_df$from,edge_df$to)),
stringsAsFactors = F)
tail(nodes_df,10)Antara data edge dan nodes belum terlalu rapi, masih ada quotation mark (" "), saya ingin membersikan menggunakan regex pada function str_replace_all
nodes_df1 <- nodes_df %>%
mutate(name = str_replace_all(string = name,
pattern = "\"",
replacement = ""))
head(nodes_df1)edge_df1 <- edge_df %>%
mutate(to = str_replace_all(string = to,
pattern = "\"",
replacement = ""))
head(edge_df1)graph_tweets <- tbl_graph(nodes = nodes_df1,
edges = edge_df1,
directed = F)
graph_tweets#> # A tbl_graph: 18490 nodes and 33011 edges
#> #
#> # An undirected multigraph with 1882 components
#> #
#> # Node Data: 18,490 x 1 (active)
#> name
#> <chr>
#> 1 MdMurad46986696
#> 2 artur_litau
#> 3 uzayhayat17
#> 4 _andierna_
#> 5 Cardatson
#> 6 minguinnnnn_
#> # ... with 18,484 more rows
#> #
#> # Edge Data: 33,011 x 2
#> from to
#> <int> <int>
#> 1 1 9504
#> 2 2 9505
#> 3 2 9506
#> # ... with 33,008 more rows
Saya menggunakan undirected graph karena ingin mengabaikan arah interaksi antara from dan to, dan apakah interaksi terjadi secara dua arah atau searah.
Kita akan mengukur centrality dari setiap Nodes dengan parameter berikut:
Degree: untuk menemukan user yang terkoneksi secara lokal (dalam satu cluster)
Betweenness: untuk menemukan user yang menjadi perantara terhadap beberapa cluster
Closeness: untuk menemukan user yang memiliki bisa memberi influence tercepat terhadap yang lain
Eigen: mengukur pengaruh dari setiap user dari banyaknya keterhubungan dengan node-node lainnya; lalu dipertimbangkan juga koneksi yang dimiliki oleh setiap user; bobot dari berbagai koneksi yang dimiliki
options(scipen = 100)
graph_tweets <- graph_tweets %>%
activate(nodes) %>%
mutate(degree = centrality_degree(), # Calculate degree centrality
between = centrality_betweenness(normalized = T), # Calculate betweeness centrality
closeness = centrality_closeness(), # Calculate closeness centrality
eigen = centrality_eigen()
) # Calculate eigen centrality
graph_tweets#> # A tbl_graph: 18490 nodes and 33011 edges
#> #
#> # An undirected multigraph with 1882 components
#> #
#> # Node Data: 18,490 x 5 (active)
#> name degree between closeness eigen
#> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 MdMurad46986696 1 0 0.00000000950 1.92e- 7
#> 2 artur_litau 3 0.000112 0.00000000949 3.21e-11
#> 3 uzayhayat17 4 0.0000000173 0.00000000950 6.88e- 2
#> 4 _andierna_ 3 0.000000984 0.00000000950 1.12e- 3
#> 5 Cardatson 2 0.0000000117 0.00000000293 0
#> 6 minguinnnnn_ 3 0.0000130 0.00000000950 7.81e- 5
#> # ... with 18,484 more rows
#> #
#> # Edge Data: 33,011 x 2
#> from to
#> <int> <int>
#> 1 1 9504
#> 2 2 9505
#> 3 2 9506
#> # ... with 33,008 more rows
network_act_df <- graph_tweets %>%
activate(nodes) %>%
as.data.frame()
head(network_act_df,10)kp_activity <- data.frame(
network_act_df %>% arrange(-degree) %>% select(name) %>% slice(1:6),
network_act_df %>% arrange(-between) %>% select(name) %>% slice(1:6),
network_act_df %>% arrange(-closeness) %>% select(name) %>% slice(1:6),
network_act_df %>% arrange(-eigen) %>% select(name) %>% slice(1:6)
) %>% setNames(c("degree","betweenness","closeness","eigen"))
kp_activityBerdasarkan hasil analisis centralities, terlihat bahwa akun f_sara termasuk dalam semua golongan centrality, yang berarti akun tersebut paling populer secara lokal maupun global (eigen).
Selain itu, besar kemungkinan jugaf_sara terhubung dengan kebanyakan cluster, sehingga arus informasi dari semua cluster diakomodasi oleh akun tersebut.
Namun, temuan dapat saja berbeda apabila terdapat aktivitas mentioning yang berlebihan yang ditujukan pada akun f_sara, terlepas dari apa dan siapa identitas akun tersebut.
# Memahami siapa f_sara
lookup <- tweets %>%
select(screen_name, mentions_screen_name, text, retweet_count) %>%
mutate(mentions_screen_name = as.character(mentions_screen_name)) %>%
mutate(mentions_screen_name = str_replace_all(string = mentions_screen_name,
pattern = "^c\\(|\\)$",
replacement = "")) %>%
na.omit() %>%
mutate(mentions_screen_name = str_replace_all(string = mentions_screen_name,
pattern = "[[:punct]]+",
replacement = "")) %>%
mutate(mentions_screen_name = str_replace_all(string = mentions_screen_name,
pattern = "\"",
replacement = "")) %>%
separate_rows(mentions_screen_name, sep = ",")
lookupinspect_sara <- lookup %>%
filter(mentions_screen_name == "f_sara") %>%
arrange(desc(retweet_count)) %>%
distinct(text, mentions_screen_name, screen_name)
inspect_sara[41:105,]Setelah pencarian lebih lanjut, sayangnya akun dengan nama screen_name f_sara tidak terkait dengan tweet bertopik NFT/kripto.
Berdasarkan data, kita dapat melihat bahwa f_sara sering terkait dengan tweet dari sebuah akun yang bernama @nft_spartan.
nft_spartan merupakan kolektor dan investor NFT, akun tersebut sering terlibat dalam suatu givaway maupun info dan ‘tips and trick’.
Dalam dunia yang sarat dengan unsur FOMO (Fear Of Missing Out) seperti game, cryptocurrency maupun NFT, kita mungkin sering melihat akun yang “menggaungkan isu” secara intensif/spamming.
Analisis saya, karena pada saat ini kita mengacu pada mentions_screen_name, kemungkinan besar akun nft_spartan melakukan mention secara acak dan hidden kepada beberapa screen name untuk memperkenalkan isi kontennya. Namun disayangkan, karena target mentionnya terlihat seperti akun nonaktif.
set.seed(123)
graph_tweets <- graph_tweets %>%
activate(nodes) %>%
mutate(community = group_louvain()) %>%
activate(edges) %>%
filter(!edge_is_loop()) # Remove loop edgesgraph_tweets %>%
activate(nodes) %>%
as.data.frame() %>%
count(community)Mengambil cluster 1-5 saja, dengan asumsi 5 data dari klaster terbanyak sudah representatif (jumlah dari n1-n5)
# fungsi untuk mendapatkan orang orang penting di tiap cluster
important_user <- function(data) {
name_person <- data %>%
as.data.frame() %>%
filter(community %in% 1:5) %>%
select(-community) %>%
pivot_longer(-name, names_to = "measures", values_to = "values") %>%
group_by(measures) %>%
arrange(desc(values)) %>%
slice(1:6) %>%
ungroup() %>%
distinct(name) %>%
pull(name)
return(name_person)
}important_person <-
graph_tweets %>%
activate(nodes) %>%
important_user()set.seed(100)
graph_tweets %>%
activate(nodes) %>%
mutate(ids = row_number(),
community = as.character(community)) %>%
filter(community %in% 1:5) %>%
arrange(community,ids) %>%
mutate(node_label = ifelse(name %in% important_person, name, NA)) %>%
ggraph(layout = "fr") +
geom_edge_link(alpha = 0.3 ) +
geom_node_point(aes(size = degree, fill = community), shape = 21, alpha = 0.7, color = "grey30") +
geom_node_label(aes(label = node_label), repel = T, alpha = 0.8 ) +
guides(size = "none") +
labs(title = "Top 5 Community of #NFT",
color = "Interaction",
fill = "Community") +
theme_void() +
theme(legend.position = "top")Saya sedikit skeptis dengan hasil top 5 community diatas, bagaimana eksistensi akunnya di twitter, apakah sama seperti f_sara.
Maka dari itu saya ingin melihat screen_name, mentions_screen_name, dan text yang melibatkan akun yang saya curigai.
Saya tertarik untuk inspect akun missryolog.
missryolog <- lookup %>%
filter(mentions_screen_name == "missryolog") %>%
arrange(desc(retweet_count)) %>%
distinct(text, mentions_screen_name, screen_name)
head(missryolog,100)Dapat kita lihat dari isi tweet, bahwa pada akun missryolog pun masih berkaitan dengan spamming nft_spartan.
Selanjutnya, sebetulnya apa isi dari akun missryolog?
knitr::include_graphics("images/ryolog.png")Ternyata, missryolog bukanlah merupakan akun yang aktif (atau bahkan salah alamat). Dari hasil eksplorasi object yang sudah saya buat secara khusus, saya memiliki perkiraan bahwa missryolog merupakan kesalahan penulisan dari misscryptolog.
missryolog[991:1052,]knitr::include_graphics("images/cryptolog.png")Berdasarkan hasil visualisasi dan centrality, dapat disimpulkan bahwa cluster yang berwarna hijau yang memiliki keterhubungan dengan nodes dari cluster lainnya. Namun, sepertinya terdapat beberapa nama akun yang tidak valid; entah itu dikarenakan proses cleansing atau karena data asalnya (mohon pencerahan).
Banyak akun investor NFT yang melakukan spamming terhadap akun-akun lain secara acak, dilihat dari relevansi mentions_screen_name terhadap pemeriksaan eksistensi akun tersebut di twitter (contohnya: f_sara).
Akun @nft_spartan was a great spammer
library(networkD3)src <- c("A", "A", "A", "A",
"B", "B", "C", "C", "D")
target <- c("B", "C", "D", "J", "E",
"F", "G", "H", "I")
networkData <- data.frame(src, target)
simpleNetwork(networkData)data("Mislinks")
data("Misnodes")forceNetwork(Links = MisLinks, Nodes = MisNodes, Source = "source",
Target = "target", Value = "value", NodeID = "name",
Nodesize = "size",
radiusCalculation = "Math.sqrt(d.nodesize)+6",
Group = "group", opacity = 1, opacityNoHover = F, width = 1000, height = 700, fontSize = 12)