5  文書メニュー

Code
suppressPackageStartupMessages({
  library(ggplot2)
  library(duckdb)
})
drv <- duckdb::duckdb()
con <- duckdb::dbConnect(drv, dbdir = "tutorial_jp/kokoro.duckdb", read_only = TRUE)

tbl <-
  readxl::read_xls("tutorial_jp/kokoro.xls",
    col_names = c("text", "section", "chapter", "label"),
    skip = 1
  ) |>
  dplyr::mutate(
    doc_id = factor(dplyr::row_number()),
    dplyr::across(where(is.character), ~ audubon::strj_normalize(.))
  ) |>
  dplyr::filter(!gibasa::is_blank(text)) |>
  dplyr::relocate(doc_id, text, section, label, chapter)

5.1 文書検索(A.6.1)

5.1.1 TF-IDF

KWICの結果を検索語のTF-IDFの降順で並び替える例。

Code
dat <-
  dplyr::tbl(con, "tokens") |>
  dplyr::filter(section == "[1]上_先生と私") |>
  dplyr::select(label, token) |>
  dplyr::collect()

dat |>
  dplyr::reframe(token = list(token), .by = label) |>
  tibble::deframe() |>
  quanteda::as.tokens() |>
  quanteda::tokens_select("[[:punct:]]", selection = "remove", valuetype = "regex", padding = FALSE) |>
  quanteda::kwic(pattern = "^向[いくけこ]$", window = 5, valuetype = "regex") |>
  dplyr::as_tibble() |>
  dplyr::select(docname, pre, keyword, post) |>
  dplyr::left_join(
    dat |>
      dplyr::count(label, token) |>
      tidytext::bind_tf_idf(token, label, n),
    by = dplyr::join_by(docname == label, keyword == token)
  ) |>
  dplyr::arrange(desc(tf_idf))
#> # A tibble: 18 × 8
#>    docname    pre                      keyword post      n      tf   idf  tf_idf
#>    <chr>      <chr>                    <chr>   <chr> <int>   <dbl> <dbl>   <dbl>
#>  1 上・三十四 いっ た なり 下 を       向い    た …      2 0.00251  1.19 0.00298
#>  2 上・三十四 突然 奥さん の 方 を     向い    た …      2 0.00251  1.19 0.00298
#>  3 上・八     は 私 の 方 を           向い    て …      2 0.00239  1.19 0.00283
#>  4 上・八     また 私 の 方 を         向い    た …      2 0.00239  1.19 0.00283
#>  5 上・二     て 海 の 方 を           向い    て …      2 0.00217  1.19 0.00257
#>  6 上・二     まで 沖 の 方 へ         向い    て …      2 0.00217  1.19 0.00257
#>  7 上・十二   な 方角 へ 足 を         向け    た …      1 0.00117  2.20 0.00257
#>  8 上・十六   の 眼 を 私 に           向け    た …      1 0.00117  2.20 0.00256
#>  9 上・三十三 先生 は ちょっと 顔 だけ 向け    直し…     1 0.00112  2.20 0.00246
#> 10 上・二十五 た 世間 に 背中 を       向け    た …      1 0.00107  2.20 0.00236
#> 11 上・十七   う 世の中 の どっち を   向い    て …      1 0.00122  1.19 0.00145
#> 12 上・十二   花 より も そちら を     向い    て …      1 0.00117  1.19 0.00139
#> 13 上・七     は 外 の 方 を           向い    て …      1 0.00116  1.19 0.00138
#> 14 上・十四   に 庭 の 方 を           向い    た …      1 0.00115  1.19 0.00137
#> 15 上・三十三 は 庭 の 方 を           向い    て …      1 0.00112  1.19 0.00133
#> 16 上・三十五 は 庭 の 方 を           向い    て …      1 0.00110  1.19 0.00131
#> 17 上・十五   は また 奥さん と 差し   向い    で …      1 0.00109  1.19 0.00129
#> 18 上・二十六 を し て よそ を         向い    て …      1 0.00106  1.19 0.00126

5.1.2 LexRank🍳

LexRankは、TF-IDFで重みづけした文書間の類似度行列についてページランクを計算することで、文書集合のなかから「重要な文書」を抽出する手法。

Code
dat <-
  dplyr::tbl(con, "tokens") |>
  dplyr::filter(
    pos %in% c(
      "名詞", # "名詞B", "名詞C",
      "地名", "人名", "組織名", "固有名詞",
      "動詞", "未知語", "タグ"
    )
  ) |>
  dplyr::mutate(
    token = dplyr::if_else(is.na(original), token, original),
    token = paste(token, pos, sep = "/")
  )

dfm <- dat |>
  dplyr::count(label, token) |>
  dplyr::collect() |>
  tidytext::bind_tf_idf(token, label, n) |>
  dplyr::inner_join(
    dat |>
      dplyr::select(doc_id, label, token) |>
      dplyr::collect(),
    by = dplyr::join_by(label == label, token == token)
  ) |>
  tidytext::cast_dfm(doc_id, token, tf_idf)

文書間のコサイン類似度を得て、PageRankを計算する。quanteda.textstats::textstat_simil()proxyC::simil()と処理としては同じだが、戻り値がtextstat_simil_symm_sparseというS4クラスのオブジェクトになっていて、as.data.frame()で縦長のデータフレームに変換できる。

Code
scores <- dfm |>
  quanteda.textstats::textstat_simil(
    margin = "documents",
    method = "cosine",
    min_simil = .6 # LexRankの文脈でいうところのthreshold
  ) |>
  as.data.frame() |>
  dplyr::mutate(weight = 1) |> # 閾値以上のエッジしかないので、重みはすべて1にする
  # dplyr::rename(weight = cosine) |> # あるいは、閾値を指定せずに、コサイン類似度をそのまま重みとして使う(continuous LexRank)
  igraph::graph_from_data_frame(directed = FALSE) |>
  igraph::page_rank(directed = FALSE, damping = .85) |>
  purrr::pluck("vector")

LexRankは抽出型の要約アルゴリズムということになっているが、必ずしも要約的な文書が得られるわけではない。文書集合のなかでも類似度が比較的高そうな文書をn件取り出してきてサブセットをつくるみたいな使い方はできるかも?

Code
sort(scores, decreasing = TRUE) |>
  tibble::enframe() |>
  dplyr::left_join(
    dplyr::select(tbl, doc_id, text, chapter),
    by = dplyr::join_by(name == doc_id)
  ) |>
  dplyr::slice_head(n = 5)
#> # A tibble: 5 × 4
#>   name    value text                                                     chapter
#>   <chr>   <dbl> <chr>                                                    <chr>  
#> 1 1123  0.00702 「馬鹿だ」とやがてKが答えました。「僕は馬鹿だ」          3_41   
#> 2 1027  0.00490 私はKに手紙を見せました。Kは何ともいいませんでしたけれ…  3_22   
#> 3 1025  0.00461 「Kの事件が一段落ついた後で、私は彼の姉の夫から長い封書… 3_22   
#> 4 1122  0.00461 私は二度同じ言葉を繰り返しました。そうして、その言葉がK… 3_41   
#> 5 1028  0.00382 私はKと同じような返事を彼の義兄宛で出しました。その中に… 3_22

5.2 クラスター分析(A.6.2)

5.2.1 LSI🍳

文書単語行列(または、単語文書行列)に対して特異値分解をおこなって、行列の次元を削減する手法をLSIという。潜在的意味インデキシング(Latent Semantic Indexing, LSI)というのは情報検索の分野での呼び方で、自然言語処理の文脈だと潜在意味解析(Latent Semantic Analysis, LSA)というらしい。

Code
dfm <-
  dplyr::tbl(con, "tokens") |>
  dplyr::filter(
    pos %in% c(
      "名詞", "名詞B", "名詞C",
      "地名", "人名", "組織名", "固有名詞",
      "動詞", "未知語", "タグ"
    )
  ) |>
  dplyr::mutate(
    token = dplyr::if_else(is.na(original), token, original),
    token = paste(token, pos, sep = "/")
  ) |>
  dplyr::count(doc_id, token) |>
  dplyr::collect() |>
  tidytext::cast_dfm(doc_id, token, n) |>
  quanteda::dfm_trim(min_termfreq = 10) |>
  quanteda::dfm_tfidf(scheme_tf = "prop") |>
  rlang::as_function(
    ~ quanteda::dfm_subset(., quanteda::rowSums(.) > 0)
  )()

dfm
#> Document-feature matrix of: 1,099 documents, 332 features (97.22% sparse) and 0 docvars.
#>     features
#> docs   人/名詞C 打ち明ける/動詞 使う/動詞  行く/動詞 電報/名詞   男/名詞C
#>    1 0.13466723       0.1151988 0.1272277 0          0         0         
#>    2 0                0         0         0.03223983 0.1688659 0         
#>    3 0                0         0         0          0         0.08643426
#>    4 0                0         0         0          0         0         
#>    5 0.05237059       0         0         0          0         0.07202855
#>    6 0.06284471       0         0         0.07307694 0         0         
#>     features
#> docs 一つ/名詞  海/名詞C  違う/動詞  先生/名詞
#>    1 0         0         0          0.12213930
#>    2 0         0         0          0.01676422
#>    3 0         0         0          0         
#>    4 0.2960317 0.3711644 0          0         
#>    5 0         0.2062024 0          0         
#>    6 0         0.1237215 0.09509823 0.03799889
#> [ reached max_ndoc ... 1,093 more documents, reached max_nfeat ... 322 more features ]

ここでは300列くらいしかないので大したことないが、特徴量の数が多い文書単語行列をas.matrix()すると、メモリ上でのサイズが大きいオブジェクトになってしまい、扱いづらい。そこで、もとの文書単語行列のもつ情報をできるだけ保持しつつ、行列の次元を削減したいというときに、LSIを利用することができる。

とくに、文書のクラスタリングをおこなう場合では、どの語彙がどのクラスタに属する要因になっているかみたいなことはどうせ確認できないので、特徴量は適当に削減してしまって問題ないと思う。

Code
lobstr::obj_size(dfm)
#> 244.62 kB
lobstr::obj_size(as.matrix(dfm))
#> 3.02 MB

quanteda:textmodels::textmodel_lsa(margin = "documents")とすると、特異値分解(Truncated SVD)の \(D \simeq D_{k} = U_{k}\Sigma{}_{k}V^{T}_{k}\) という式における \(V_{k}\) が戻り値にそのまま残る(margin="features"だと \(U_{k}\) がそのまま残り、"both"で両方ともそのまま残る)。

特異値分解する行列 \(D\) について、いま、行側に文書・列側に単語がある持ち方をしている。ここでは、行列 \(D\) をランク \(k\) の行列 \(D_{k}\) で近似したい(ランク削減したい)というより、特徴量を減らしたい( \(k\) 列の行列にしてしまいたい)と思っているため、dfm\(V_{k}\) をかける。

Code
mat <- quanteda.textmodels::textmodel_lsa(dfm, nd = 50, margin = "documents")
mat <- dfm %*% mat$features

str(mat)
#> Formal class 'dgeMatrix' [package "Matrix"] with 4 slots
#>   ..@ Dim     : int [1:2] 1099 50
#>   ..@ Dimnames:List of 2
#>   .. ..$ docs: chr [1:1099] "1" "2" "3" "4" ...
#>   .. ..$     : NULL
#>   ..@ x       : num [1:54950] 0.1055 0.0512 0.0259 0.0294 0.0372 ...
#>   ..@ factors : list()

5.2.2 階層的クラスタリング

LSIで次元を削減した行列について、クラスタリングをおこなう。ここでは、文書間の距離としてコサイン距離を使うことにする。

文書間の距離のイメージ。

Code
g1 <- mat |>
  proxyC::simil(margin = 1, method = "ejaccard") |>
  rlang::as_function(~ 1 - .[1:100, 1:100])() |>
  as.dist() |>
  factoextra::fviz_dist() +
  theme(axis.text.x = element_blank(), axis.text.y = element_blank()) +
  labs(title = "ejaccard")

g2 <- mat |>
  proxyC::simil(margin = 1, method = "edice") |>
  rlang::as_function(~ 1 - .[1:100, 1:100])() |>
  as.dist() |>
  factoextra::fviz_dist() +
  theme(axis.text.x = element_blank(), axis.text.y = element_blank()) +
  labs(title = "edice")

g3 <- mat |>
  proxyC::simil(margin = 1, method = "cosine") |>
  rlang::as_function(~ 1 - .[1:100, 1:100])() |>
  as.dist() |>
  factoextra::fviz_dist() +
  theme(axis.text.x = element_blank(), axis.text.y = element_blank()) +
  labs(title = "cosine")

patchwork::wrap_plots(g1, g2, g3, nrow = 3)
#> Warning in vp$just: 'just' の 'justification' への部分的マッチ

#> Warning in vp$just: 'just' の 'justification' への部分的マッチ

#> Warning in vp$just: 'just' の 'justification' への部分的マッチ

階層的クラスタリングは非階層的なアルゴリズムに比べると計算量が多いため、個体数が増えるとクラスタリングするのにやや時間がかかることがある。

Code
dat <- mat |>
  proxyC::simil(margin = 1, method = "cosine") |>
  rlang::as_function(~ 1 - .)()

clusters <-
  as.dist(dat) |>
  hclust(method = "ward.D2")

cluster::silhouette(cutree(clusters, k = 5), dist = dat) |>
  factoextra::fviz_silhouette(print.summary = FALSE) +
  theme_classic() +
  theme(axis.text.x = element_blank())
#> Warning in vp$just: 'just' の 'justification' への部分的マッチ

5.2.3 非階層的クラスタリング🍳

必ずしもクラスタの階層構造を確認したいわけではない場合、kmeans()だと計算が早いかもしれない。

ただ、「K-meansはクラスタ中心からのユークリッド距離でクラスタを分ける」(機械学習帳)ため、特徴量の数が増えてくるとクラスタの比率がおかしくなりがち。

Code
clusters <- kmeans(mat, centers = 5, iter.max = 100, algorithm = "Lloyd")

cluster::silhouette(clusters$cluster, dist = dat) |>
  factoextra::fviz_silhouette(print.summary = FALSE) +
  theme_classic() +
  theme(axis.text.x = element_blank())
#> Warning in vp$just: 'just' の 'justification' への部分的マッチ

spherical k-meansの実装であるskmeansだとクラスタの比率はいくらかマシになるかもしれない。

Code
clusters <-
  skmeans::skmeans(
    as.matrix(mat),
    k = 5,
    method = "pclust",
    control = list(maxiter = 100)
  )

cluster::silhouette(clusters$cluster, dist = dat) |>
  factoextra::fviz_silhouette(print.summary = FALSE) +
  theme_classic() +
  theme(axis.text.x = element_blank())
#> Warning in vp$just: 'just' の 'justification' への部分的マッチ

5.3 トピックモデル(A.6.3-4)

5.3.1 トピック数の探索

LDAのトピック数の探索は、実際にfitしてみて指標のよかったトピック数を採用するみたいなやり方をする。

Code
dfm <-
  dplyr::tbl(con, "tokens") |>
  dplyr::filter(
    pos %in% c(
      "名詞", "名詞B", "名詞C",
      "地名", "人名", "組織名", "固有名詞",
      "動詞", "未知語", "タグ"
    )
  ) |>
  dplyr::mutate(
    token = dplyr::if_else(is.na(original), token, original),
    token = paste(token, pos, sep = "/")
  ) |>
  dplyr::count(doc_id, token) |>
  dplyr::collect() |>
  tidytext::cast_dfm(doc_id, token, n)

ここでは、トピック数を5から15まで変化させる。seededlda::textmodel_lda(auto_iter=TRUE)とすると、ステップがmax_iter以下であっても条件によってギブスサンプリングを打ち切る挙動になる。

Code
divergence <-
  purrr::map(5:15, \(.x) {
    lda_fit <-
      seededlda::textmodel_lda(dfm, k = .x, batch_size = 0.2, auto_iter = TRUE, verbose = FALSE)
    tibble::tibble(
      topics = .x,
      Deveaud2014 = seededlda::divergence(lda_fit, regularize = FALSE),
      WatanabeBaturo2023 = seededlda::divergence(lda_fit, min_size = .04, regularize = TRUE)
    )
  }) |>
  purrr::list_rbind()
#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

#> Warning in x$word: 'word' の 'words' への部分的マッチ

Deveaud2014という列は、ldatuningで確認できる同名の値と同じ指標。WatanabeBaturo2023という列は、Deveaud2014についてトピックの比率が閾値を下回るときにペナルティを加えるように修正した指標。どちらも大きいほうがよい指標なので、基本的には値が大きくなっているトピック数を選ぶ。

Code
ggplot(divergence, aes(topics)) +
  geom_line(aes(y = Deveaud2014, color = "Deveaud2014")) +
  geom_line(aes(y = WatanabeBaturo2023, color = "WatanabeBaturo2023")) +
  scale_x_continuous(breaks = 5:15) +
  theme_bw() +
  ylab("Divergence")
#> Warning in vp$just: 'just' の 'justification' への部分的マッチ

5.3.2 Distributed LDA

Code
lda_fit <-
  seededlda::textmodel_lda(dfm, k = 9, batch_size = 0.2, verbose = FALSE)

seededlda::sizes(lda_fit)
#>     topic1     topic2     topic3     topic4     topic5     topic6     topic7 
#> 0.09943469 0.15324046 0.14395316 0.10877246 0.11185140 0.10473450 0.10725823 
#>     topic8     topic9 
#> 0.08318191 0.08757319

5.3.3 トピックとその出現位置

Code
dat <- tbl |>
  dplyr::transmute(
    doc_id = doc_id,
    topic = seededlda::topics(lda_fit)[as.character(doc_id)],
  ) |>
  dplyr::filter(!is.na(topic)) # dfmをつくった時点で単語を含まない文書はトピックの割り当てがないため、取り除く

dat |>
  ggplot(aes(x = doc_id)) +
  geom_raster(aes(y = topic, fill = topic), show.legend = FALSE) +
  theme_classic() +
  theme(axis.text.x = element_blank())

5.3.4 単語の生起確率

Code
dat <-
  t(lda_fit$phi) |>
  dplyr::as_tibble(
    .name_repair = ~ paste0("topic", seq_along(.)),
    rownames = "word"
  ) |>
  tidyr::pivot_longer(starts_with("topic"), names_to = "topic", values_to = "phi") |>
  dplyr::mutate(phi = signif(phi, 3)) |>
  dplyr::slice_max(phi, n = 20, by = topic)

reactable::reactable(
  dat,
  filterable = TRUE,
  defaultColDef = reactable::colDef(
    cell = reactablefmtr::data_bars(dat, text_position = "outside-base")
  )
)

5.4 ナイーブベイズ(A.6.6-8)

quanteda.textmodels::textmodel_nb()で分類する例。ここでは、LexRankの節で抽出したscoresの付いている文書を使って学習する。交差検証はしない。

Code
dfm <-
  dplyr::tbl(con, "tokens") |>
  dplyr::filter(
    pos %in% c(
      "名詞", # "名詞B", "名詞C",
      "地名", "人名", "組織名", "固有名詞",
      "動詞", "未知語", "タグ"
    )
  ) |>
  dplyr::mutate(
    token = dplyr::if_else(is.na(original), token, original),
    token = paste(token, pos, sep = "/")
  ) |>
  dplyr::count(doc_id, token) |>
  dplyr::collect() |>
  tidytext::cast_dfm(doc_id, token, n) |>
  quanteda::dfm_tfidf(scheme_tf = "prop")

labels <- tbl |>
  dplyr::mutate(section = factor(section, labels = c("上", "中", "下"))) |>
  dplyr::filter(doc_id %in% quanteda::docnames(dfm)) |>
  dplyr::pull(section, doc_id)

nb_fit <- dfm |>
  quanteda::dfm_subset(
    quanteda::docnames(dfm) %in% names(scores)
  ) |>
  rlang::as_function(~ {
    # dfmに格納すると文書の順番が入れ替わるので、labelsの順番をあわせなければならない
    quanteda.textmodels::textmodel_nb(., labels[quanteda::docnames(.)])
  })()

dat <- tbl |>
  dplyr::mutate(section = factor(section, labels = c("上", "中", "下"))) |>
  dplyr::filter(doc_id %in% quanteda::docnames(dfm)) |>
  dplyr::mutate(.pred = predict(nb_fit, dfm)[as.character(doc_id)]) # 予測値の順番をあわせる必要がある

yardstick::conf_mat(dat, section, .pred) # 混同行列
#>           Truth
#> Prediction  上  中  下
#>         上 491 103  60
#>         中  43 140  11
#>         下  22  30 229

yardstick::accuracy(dat, section, .pred) # 正解率
#> # A tibble: 1 × 3
#>   .metric  .estimator .estimate
#>   <chr>    <chr>          <dbl>
#> 1 accuracy multiclass     0.762

yardstick::f_meas(dat, section, .pred) # F値
#> # A tibble: 1 × 3
#>   .metric .estimator .estimate
#>   <chr>   <chr>          <dbl>
#> 1 f_meas  macro          0.733

精度よく分類することよりも、各カテゴリにおける「スコア」(尤度)の比を見るのが目的でナイーブベイズを使っているはずなので、確認する。

Code
dat <-
  coef(nb_fit) |>
  dplyr::as_tibble(rownames = "token") |>
  rlang::as_function(~ {
    s <- t(coef(nb_fit)) |> colSums()
    dplyr::mutate(.,
      across(where(is.numeric), ~ . / s),
      var = t(coef(nb_fit)) |> cov() |> diag(),
      across(where(is.numeric), ~ signif(., 3))
    )
  })() |>
  dplyr::slice_max(var, n = 50)

reactable::reactable(
  dat,
  filterable = TRUE,
  defaultColDef = reactable::colDef(
    cell = reactablefmtr::data_bars(dat, text_position = "outside-base")
  )
)

Code
duckdb::dbDisconnect(con)
duckdb::duckdb_shutdown(drv)

sessioninfo::session_info(info = "packages")
#> ═ Session info ═══════════════════════════════════════════════════════════════
#> ─ Packages ───────────────────────────────────────────────────────────────────
#>  package             * version    date (UTC) lib source
#>  abind                 1.4-5      2016-07-21 [1] RSPM (R 4.3.0)
#>  audubon               0.5.1.9001 2023-09-03 [1] https://paithiov909.r-universe.dev (R 4.3.1)
#>  backports             1.4.1      2021-12-13 [1] CRAN (R 4.3.0)
#>  blob                  1.2.4      2023-03-17 [1] RSPM (R 4.3.0)
#>  broom                 1.0.5      2023-06-09 [1] RSPM (R 4.3.0)
#>  cachem                1.0.8      2023-05-01 [1] CRAN (R 4.3.0)
#>  car                   3.1-2      2023-03-30 [1] RSPM (R 4.3.0)
#>  carData               3.0-5      2022-01-06 [1] RSPM (R 4.3.0)
#>  cellranger            1.1.0      2016-07-27 [1] RSPM (R 4.3.0)
#>  class                 7.3-22     2023-05-03 [4] CRAN (R 4.3.1)
#>  cli                   3.6.2      2023-12-11 [1] RSPM (R 4.3.0)
#>  clue                  0.3-65     2023-09-23 [1] RSPM (R 4.3.3)
#>  cluster               2.1.6      2023-12-01 [4] CRAN (R 4.3.2)
#>  codetools             0.2-19     2023-02-01 [4] CRAN (R 4.2.2)
#>  colorspace            2.1-0      2023-01-23 [1] RSPM (R 4.3.0)
#>  crosstalk             1.2.1      2023-11-23 [1] RSPM (R 4.3.0)
#>  curl                  5.2.1      2024-03-01 [1] CRAN (R 4.3.3)
#>  data.table            1.15.2     2024-02-29 [1] RSPM (R 4.3.0)
#>  DBI                 * 1.2.2      2024-02-16 [1] CRAN (R 4.3.2)
#>  dbplyr                2.4.0      2023-10-26 [1] RSPM (R 4.3.0)
#>  dials                 1.2.1      2024-02-22 [1] RSPM (R 4.3.0)
#>  DiceDesign            1.10       2023-12-07 [1] RSPM (R 4.3.0)
#>  digest                0.6.34     2024-01-11 [1] CRAN (R 4.3.2)
#>  dplyr                 1.1.4      2023-11-17 [1] CRAN (R 4.3.2)
#>  duckdb              * 0.9.2-1    2023-11-28 [1] RSPM (R 4.3.2)
#>  ellipsis              0.3.2      2021-04-29 [1] CRAN (R 4.3.0)
#>  evaluate              0.23       2023-11-01 [1] CRAN (R 4.3.1)
#>  factoextra            1.0.7      2020-04-01 [1] RSPM (R 4.3.2)
#>  fansi                 1.0.6      2023-12-08 [1] CRAN (R 4.3.2)
#>  farver                2.1.1      2022-07-06 [1] RSPM (R 4.3.0)
#>  fastmap               1.1.1      2023-02-24 [1] CRAN (R 4.3.0)
#>  fastmatch             1.1-4      2023-08-18 [1] RSPM (R 4.3.0)
#>  foreach               1.5.2      2022-02-02 [1] RSPM (R 4.3.0)
#>  furrr                 0.3.1      2022-08-15 [1] RSPM (R 4.3.0)
#>  future                1.33.1     2023-12-22 [1] RSPM (R 4.3.0)
#>  future.apply          1.11.1     2023-12-21 [1] RSPM (R 4.3.0)
#>  generics              0.1.3      2022-07-05 [1] CRAN (R 4.3.0)
#>  ggplot2             * 3.5.0      2024-02-23 [1] CRAN (R 4.3.2)
#>  ggpubr                0.6.0      2023-02-10 [1] RSPM (R 4.3.0)
#>  ggrepel               0.9.5      2024-01-10 [1] CRAN (R 4.3.2)
#>  ggsignif              0.6.4      2022-10-13 [1] RSPM (R 4.3.0)
#>  gibasa                1.1.0      2024-02-17 [1] https://paithiov909.r-universe.dev (R 4.3.2)
#>  glmnet                4.1-8      2023-08-22 [1] CRAN (R 4.3.1)
#>  globals               0.16.3     2024-03-08 [1] RSPM (R 4.3.0)
#>  glue                  1.7.0      2024-01-09 [1] CRAN (R 4.3.2)
#>  gower                 1.0.1      2022-12-22 [1] RSPM (R 4.3.0)
#>  GPfit                 1.0-8      2019-02-08 [1] RSPM (R 4.3.0)
#>  gtable                0.3.4      2023-08-21 [1] CRAN (R 4.3.1)
#>  hardhat               1.3.1      2024-02-02 [1] RSPM (R 4.3.0)
#>  htmltools             0.5.7      2023-11-03 [1] RSPM (R 4.3.0)
#>  htmlwidgets           1.6.4      2023-12-06 [1] RSPM (R 4.3.0)
#>  httpgd                1.3.1      2023-01-30 [1] CRAN (R 4.3.0)
#>  igraph                2.0.2      2024-02-17 [1] RSPM (R 4.3.0)
#>  ipred                 0.9-14     2023-03-09 [1] RSPM (R 4.3.0)
#>  iterators             1.0.14     2022-02-05 [1] RSPM (R 4.3.0)
#>  janeaustenr           1.0.0      2022-08-26 [1] RSPM (R 4.3.0)
#>  jsonlite              1.8.8      2023-12-04 [1] CRAN (R 4.3.2)
#>  knitr                 1.45       2023-10-30 [1] CRAN (R 4.3.1)
#>  labeling              0.4.3      2023-08-29 [1] RSPM (R 4.3.0)
#>  later                 1.3.2      2023-12-06 [1] RSPM (R 4.3.0)
#>  lattice               0.22-5     2023-10-24 [4] CRAN (R 4.3.1)
#>  lava                  1.8.0      2024-03-05 [1] RSPM (R 4.3.0)
#>  lhs                   1.1.6      2022-12-17 [1] RSPM (R 4.3.0)
#>  LiblineaR             2.10-23    2023-12-11 [1] RSPM (R 4.3.0)
#>  lifecycle             1.0.4      2023-11-07 [1] RSPM (R 4.3.0)
#>  listenv               0.9.1      2024-01-29 [1] RSPM (R 4.3.0)
#>  lobstr                1.1.2      2022-06-22 [1] RSPM (R 4.3.0)
#>  lubridate             1.9.3      2023-09-27 [1] RSPM (R 4.3.0)
#>  magrittr              2.0.3      2022-03-30 [1] CRAN (R 4.3.0)
#>  MASS                  7.3-60     2023-05-04 [4] CRAN (R 4.3.1)
#>  Matrix                1.6-3      2023-11-14 [4] CRAN (R 4.3.2)
#>  memoise               2.0.1      2021-11-26 [1] CRAN (R 4.3.0)
#>  munsell               0.5.0      2018-06-12 [1] RSPM (R 4.3.0)
#>  nnet                  7.3-19     2023-05-03 [4] CRAN (R 4.3.1)
#>  nsyllable             1.0.1      2022-02-28 [1] RSPM (R 4.3.0)
#>  parallelly            1.37.1     2024-02-29 [1] RSPM (R 4.3.0)
#>  parsnip               1.2.0      2024-02-16 [1] CRAN (R 4.3.2)
#>  patchwork             1.2.0      2024-01-08 [1] RSPM (R 4.3.0)
#>  pillar                1.9.0      2023-03-22 [1] CRAN (R 4.3.0)
#>  pkgconfig             2.0.3      2019-09-22 [1] CRAN (R 4.3.0)
#>  plyr                  1.8.9      2023-10-02 [1] RSPM (R 4.3.0)
#>  prettyunits           1.2.0      2023-09-24 [1] RSPM (R 4.3.0)
#>  prodlim               2023.08.28 2023-08-28 [1] RSPM (R 4.3.0)
#>  proxyC                0.3.4      2023-10-25 [1] RSPM (R 4.3.0)
#>  purrr                 1.0.2      2023-08-10 [1] RSPM (R 4.3.0)
#>  quanteda              3.3.1      2023-05-18 [1] RSPM (R 4.3.0)
#>  quanteda.textmodels   0.9.6      2023-03-22 [1] RSPM (R 4.3.0)
#>  quanteda.textstats    0.96.4     2023-11-02 [1] CRAN (R 4.3.1)
#>  R.cache               0.16.0     2022-07-21 [1] CRAN (R 4.3.0)
#>  R.methodsS3           1.8.2      2022-06-13 [1] CRAN (R 4.3.0)
#>  R.oo                  1.26.0     2024-01-24 [1] RSPM (R 4.3.0)
#>  R.utils               2.12.3     2023-11-18 [1] CRAN (R 4.3.2)
#>  R6                    2.5.1      2021-08-19 [1] CRAN (R 4.3.0)
#>  Rcpp                  1.0.12     2024-01-09 [1] RSPM (R 4.3.0)
#>  RcppParallel          5.1.7      2023-02-27 [1] CRAN (R 4.3.0)
#>  reactable             0.4.4      2023-03-12 [1] RSPM (R 4.3.0)
#>  reactablefmtr         2.0.0      2022-03-16 [1] RSPM (R 4.3.2)
#>  reactR                0.5.0      2023-10-11 [1] RSPM (R 4.3.0)
#>  readxl                1.4.3      2023-07-06 [1] RSPM (R 4.3.0)
#>  recipes               1.0.10     2024-02-18 [1] RSPM (R 4.3.0)
#>  reshape2              1.4.4      2020-04-09 [1] RSPM (R 4.3.0)
#>  rlang                 1.1.3      2024-01-10 [1] CRAN (R 4.3.2)
#>  rmarkdown             2.26       2024-03-05 [1] RSPM (R 4.3.0)
#>  rpart                 4.1.23     2023-12-05 [4] CRAN (R 4.3.2)
#>  rsample               1.2.0      2023-08-23 [1] RSPM (R 4.3.0)
#>  RSpectra              0.16-1     2022-04-24 [1] RSPM (R 4.3.0)
#>  rstatix               0.7.2      2023-02-01 [1] RSPM (R 4.3.0)
#>  sass                  0.4.8      2023-12-06 [1] RSPM (R 4.3.0)
#>  scales                1.3.0      2023-11-28 [1] RSPM (R 4.3.0)
#>  seededlda             1.1.0      2023-07-01 [1] RSPM (R 4.3.0)
#>  sessioninfo           1.2.2      2021-12-06 [1] RSPM (R 4.3.0)
#>  shape                 1.4.6.1    2024-02-23 [1] CRAN (R 4.3.2)
#>  skmeans               0.2-16     2023-09-23 [1] RSPM (R 4.3.3)
#>  slam                  0.1-50     2022-01-08 [1] RSPM (R 4.3.0)
#>  SnowballC             0.7.1      2023-04-25 [1] RSPM (R 4.3.0)
#>  SparseM               1.81       2021-02-18 [1] RSPM (R 4.3.0)
#>  stopwords             2.3        2021-10-28 [1] RSPM (R 4.3.0)
#>  stringi               1.8.3      2024-01-28 [1] https://gagolews.r-universe.dev (R 4.3.2)
#>  stringr               1.5.1      2023-11-14 [1] RSPM (R 4.3.0)
#>  styler                1.10.2     2023-08-29 [1] RSPM (R 4.3.0)
#>  survival              3.5-7      2023-08-14 [4] CRAN (R 4.3.1)
#>  systemfonts           1.0.6      2024-03-07 [1] RSPM (R 4.3.0)
#>  tibble                3.2.1      2023-03-20 [1] CRAN (R 4.3.0)
#>  tidyr                 1.3.1      2024-01-24 [1] RSPM (R 4.3.0)
#>  tidyselect            1.2.0      2022-10-10 [1] CRAN (R 4.3.0)
#>  tidytext              0.4.1      2023-01-07 [1] RSPM (R 4.3.0)
#>  timechange            0.3.0      2024-01-18 [1] RSPM (R 4.3.0)
#>  timeDate              4032.109   2023-12-14 [1] RSPM (R 4.3.0)
#>  tokenizers            0.3.0      2022-12-22 [1] RSPM (R 4.3.0)
#>  tune                  1.1.2      2023-08-23 [1] RSPM (R 4.3.0)
#>  utf8                  1.2.4      2023-10-22 [1] RSPM (R 4.3.0)
#>  V8                    4.4.2      2024-02-15 [1] CRAN (R 4.3.2)
#>  vctrs                 0.6.5      2023-12-01 [1] CRAN (R 4.3.2)
#>  withr                 3.0.0      2024-01-16 [1] RSPM (R 4.3.0)
#>  workflows             1.1.4      2024-02-19 [1] CRAN (R 4.3.2)
#>  xfun                  0.42       2024-02-08 [1] CRAN (R 4.3.2)
#>  yaml                  2.3.8      2023-12-11 [1] RSPM (R 4.3.0)
#>  yardstick             1.3.0      2024-01-19 [1] CRAN (R 4.3.2)
#> 
#>  [1] /home/paithiov909/R/x86_64-pc-linux-gnu-library/4.3
#>  [2] /usr/local/lib/R/site-library
#>  [3] /usr/lib/R/site-library
#>  [4] /usr/lib/R/library
#> 
#> ──────────────────────────────────────────────────────────────────────────────