理系夫婦のうたたねブログ

理系夫婦が好きなことを書いていきます。たまに医学っぽいことを書いていますが、あくまで私見です。

二元配置でのカウントデータの解析

はじめに

生物系のデータ解析というと、大抵ANOVAとt検定です。1 しかしANOVAは前提条件が多く存在し、データの正規性や等分散性が満たされていない場合は正しい結果が得られないことがあります。 正規性が満たされていない場合に選択肢として上がるのが、ノンパラメトリックな方法です。2郡比較の場合にはMann-WhitneyのU検定が便利に使えます。またANOVAのノンパラメトリックな代替としては、Kruskal-Wallis検定が選択されることが多いです。 ただ、これは教科書でもよく書いてありますが、二元配置デザインの場合にはノンパラメトリックな方法として広く受け入れられる手法がないです。

一方で二元配置デザイン (例えば2ストレインで2条件) でカウントデータをとることは結構ある気がします。 このデータに対して、いくつかの解析方法を試してみました。不勉強なところも多いので、間違いがあれば指摘していただけると幸いです。

データ生成

仮想データはStrain A, B に対して Condition A, Bを用意し、ポアソン分布からサンプルサイズ Nでデータを生成します。 Strain BのCondition Bのλだけ違う値にして、いくつかの解析を行ってみます。

library(tidyverse)
library(MASS)       
cmap = c("#333333", "#FF4B00", "#005AFF", "#03AF7A",
         "#4DC4FF", "#F6AA00", "#FFF100", "#B5EAD7")

sample_size <- 30
alpha <- 0.05
n_sim <- 1000

# make dataset
set.seed(1)
# number of samples for each group
# あとで再利用できるように関数にしておく
make_dataset <- function(N) {
  StrainAConditionA <- rpois(N, 0.2)
  StrainAConditionB <- rpois(N, 0.3)
  StrainBConditionA <- rpois(N, 0.3)
  StrainBConditionB <- rpois(N, 1)
  # make tibble for dataset
  data <- tibble::tibble(
    Strain = factor(rep(c("A", "A", "B", "B"), each = N)),
    Condition = factor(rep(c("A", "B", "A", "B"), each = N)),
    Value = c(StrainAConditionA, StrainAConditionB, StrainBConditionA, StrainBConditionB)
  )
  return(data)
}

# box plot using ggplot2
# 簡単に図示してみる。
data <- make_dataset(sample_size)
ggplot2::ggplot(data, aes(x = Strain, y = Value, fill = Condition)) +
  geom_boxplot() +
  scale_fill_manual(values =cmap)+
  theme_classic()

箱ひげ図

なんとなくBのデータだけ違う印象はあります。 ストレインによって、Conditionに対する応答が異なっているので、Interactionがありそうです。 実際に以下の解析方法でInteractionをどの程度有意として出してくるのかみてみます。

各手法での解析

Two-way ANOVA

まずはTwo-way ANOVAを行いました。ANOVAの場合、データには正規性が仮定されています。 カウントデータの場合、正規性の仮定は満たされないことが多い 2 気がしますが、実際こういう解析は論文でもよくみます。

ちょっと横道にそれますが、どんな統計手法を使うかは正しさの他に周りの環境にもよる気がします。単著ならなんでもいいと思うのですが、、、 個人的には、正しい統計モデルというのが存在しないのと同様に、正しい解析手法も存在しないかとおもいます。どうせ統計の専門家に見せたら生物の論文で使われている手法なんて間違いだらけなんでしょうし。 大切なのは勉強をし続けることと、その時点で自分の考えるベストな手法を選択することだと思っております。 私はまだまだ分からないことばかりですが。

閑話休題、Two-way ANOVAで解析してみます。 データを生成してTwo way ANOVA, interactionのp値を格納 これを1000回繰り返してプロットしてみます。

# Two-way ANOVA
# 1000回繰り返してp値を格納
pvalues <- replicate(n_sim, {
  data <- make_dataset(sample_size)
  aov_res <- aov(Value ~ Strain * Condition, data = data)
  summary(aov_res)[[1]][["Pr(>F)"]][3]
})
# plot p-values
ggplot(tibble::tibble(pvalues = pvalues), aes(x = pvalues)) +
  geom_histogram(bins = 30) +
  theme_classic()
# count how many p-values are less than 0.05
sum(pvalues < alpha)

conf_interval1 <- prop.test(sum(pvalues < alpha), n_sim)$conf.int

P値の分布はこのようになり、

pvalues

0.05を下回る率は65%程度でした。

glm with Poisson distribution

今度は一般化線形モデルで解析してみます。今回のようなカウントデータの場合にはデータが非負になります。カウントデータに対して正規分布を仮定して解析を行うと、推定値として負の値がでるおかしな解析をしていることになります。 この辺りはこの本が大変わかりやすくてよかったです。

Rではglmで一般化線形モデルを作成できるので、こちらで二元配置デザインのデータ解析を行います。 まずは分布としてはポアソン分布を使用します。

# glm with Poisson distribution
# 1000回繰り返してp値を格納
pvalues <- replicate(n_sim, {
  data <- make_dataset(sample_size)
  glm_res <- glm(Value ~ Strain * Condition, data = data, family = poisson(link="log"))
  summary(glm_res)$coefficients[, 4][4]
})
# plot p-values
ggplot(tibble::tibble(pvalues = pvalues), aes(x = pvalues)) +
  geom_histogram(bins = 30) +
  theme_classic()
# count how many p-values are less than 0.05
sum(pvalues < alpha)

conf_interval2 <- prop.test(sum(pvalues < alpha), n_sim)$conf.int

glm with negative binomial distribution

先ほどの解析ではポアソン分布を用いました。ポアソン分布は平均と分散が等しくλとなるような分布ですが、実際に現場で得られるデータは分散が平均よりも大きくなることがしばしばあります。過分散と呼ばれる現象です。過分散に対する手法としてはいくつか知られていますが、ここでは負の二項分布を用いたモデリングを試してみます。 (ただし今回試しているのはポアソン分布からのデータ生成なので、負の二項分布を用いてよりよいモデリングになるかどうかは微妙なところ) MASSパッケージを利用して解析を行います。

# glm with negative binomial distribution
# 1000回繰り返してp値を格納
pvalues <- replicate(n_sim, {
  data <- make_dataset(sample_size)
  glm_res <- glm.nb(Value ~ Strain * Condition, data = data)
  summary(glm_res)$coefficients[, 4][4]
})
# plot p-values
ggplot(tibble::tibble(pvalues = pvalues), aes(x = pvalues)) +
  geom_histogram(bins = 30) +
  theme_classic()
# count how many p-values are less than 0.05
sum(pvalues < alpha)

conf_interval3 <- prop.test(sum(pvalues < alpha), n_sim)$conf.int

Aligned rank transformation (ART)

最後に、Aligned rank transformation (ART) を使ってみます。 雑な理解ですが、ARTはデータをランクに変換してから解析する手法です。
ランクに変換する前に、align 3 という操作をしているのですが、これは各データからmain effectやinteractionを引いているようです。 その後にデータをランクに変換して、通常のANOVAをかける というのが手順になっているようです。

この手法が結構検出力が高いというので、試してみました。 RにはARToolというARTを実行するためのパッケージがあります。 かなり使い勝手は良く、anova()関数内にARTを指定するだけで良いので、簡単に使えます。 本来はart()で一度実行して、きちんと変換がうまくいっているかをチェックするべきですが、 今回は飛ばします。

# install.packages("ARTool")  
library(ARTool)
# 1000回繰り返してp値を格納
pvalues <- replicate(n_sim, {
  data <- make_dataset(sample_size)
  art_res <- anova(art(Value ~ Strain * Condition, data = data))
  art_res$`Pr(>F)`[3]
})
# plot p-values
ggplot(tibble::tibble(pvalues = pvalues), aes(x = pvalues)) +
  geom_histogram(bins = 30) +
  theme_classic()
# count how many p-values are less than 0.05
sum(pvalues < alpha)

conf_interval4 <- prop.test(sum(pvalues < alpha), n_sim)$conf.int

信頼区間の比較

それぞれの手法で得られた検出力の信頼区間を比較してみます。

# use conf_interval1, conf_interval2, conf_interval3 to compare
# make dataframe from conf_interval1, conf_interval2, conf_interval3
data <- data.frame(
  Method = rep(c("Two-way ANOVA",
                   "glm with poisson",
                  "glm with negative binomial",
                   "ART"), each = 2),
  Power = c(conf_interval1,
            conf_interval2,
            conf_interval3,
            conf_interval4)
)
# 平均値の計算
means <- aggregate(Power ~ Method, data, mean)

# エラーバーの計算(最大値と最小値の差)
errors <- aggregate(Power ~ Method, data, function(x) max(x) - min(x))

# 平均値とエラーバーを結合
means$Error <- errors$Power / 2

# Means のデータ並び順を変更
# two-way ANOVA, glm with poisson, ARTの順に
means$Method <- factor(means$Method, levels = c("ART",
                                                "glm with negative binomial",
                                                "glm with poisson",
                                                  "Two-way ANOVA"   ))

p <- ggplot(means, aes(x = Method, y = Power)) +
  geom_point(size = 3) +
  geom_errorbar(aes(ymin = Power - Error, ymax = Power + Error), width = 0.2) +
  labs(title = "Mean statistical powers with Error Bars", x = "Methods", y = "Powers") +
  theme_classic()+ 
  theme(text = element_text(size = 18)) + 
  coord_flip()

p

信頼区間

各手法で1000回中何回interactionが有意になったかと、その回数の95%信頼区間をグラフにしました。 この回数というのは検出力と考えられると思います。 (H0が正しくないときの棄却回数なので と書こうとしてこの辺りはかなり混迷を極めているのを思い出しました。ちょっと自分は理解しきれていませんが、以下の本に詳しかったと思います。)

こうしてみると、検出力からだけでいえば、ARTとTwo way Anova が高い検出力を持っているようです。 他の手法はのちに言及するとして、ARTは検出力が高いようで、使うのも簡単ですしかなり良い印象を持ちました。 ARTのデメリットとしては、モデリングの際に使用されているのがランク変換済みのデータのため、推定されたパラメータの意味は理解しにくいという面があります。例えばポアソン分布でglmを用いた場合には、パラメータ (λ) がどう異なるかなどを議論することができると思いますが、ARTの場合にはその部分の説明が難しそうです。 よく生物で見かける、「有意かどうかを知りたい」4 という状況下であればARTがかなり良い気がしますね。

異なるデータでの再検証

ARTは予想通りだったとして、Two way Anovaが高い検出力を持っている理由は何か、もう少し調べてみたいと思います。 カウントデータがTwo way ANOVAでの解析に向いていない点としては、非負の整数であることが挙げられますが、今回のデータは0が少ないため、正規分布で近似可能かもしれません。 また、サンプルサイズが小さい場合にはglmの推定がうまくいかない可能性もあります。 そこでもっとλを小さくして、0が多く含まれるデータにして、サンプルサイズを増やしてみます。

0が多い箱ひげ図

データはこんな感じでした。

信頼区間はこんな感じになりました。

信頼区間、0が多いデータ

どうも同じような結果になりました. 色々とパラメータを変えてみたりしましたが、どうも常にTwo way Anovaがglmよりも高い検出力を持っているようです。 もう少しリアルデータっぽい汚い分布にしたらどうなるのかというのも気になりますが、それは今度時間があるときに試してみようかと思います。

まとめ

今回は、カウントデータを使って、いくつかの解析手法の検出力を比較してみました。 試した手法の中では、ARTによるANOVAが結構いい線いっている気がしました。統計モデリングとしての解析ではないのであれば、かなり良い選択肢になると思います。コードを書くのも大変ではないですし、まだ試していませんが対比較も可能なようです。ドキュメントをしっかりよみつつ、今後使ってみようと思います。

cran.r-project.org

もう一つわかったこととして、意外とポアソン分布ではTwo way anovaでも良く解析できるのかもしれません。 (単なる計算ミスや、コードの書き方のミスの可能性は大いにあるとはおもいますが) 他の分布の場合にはどうなるのかというのは興味のあるところですが、また時間のある時に試してみます。


  1. こんなの (https://pubmed.ncbi.nlm.nih.gov/34784504/) も出てきたりしているので、徐々に改善してきている?
  2. 曖昧な表現になっているのは、仮にポアソン分布に従うデータであってもλが大きければ正規分布で近似可能かなと思ったからです。
  3. 日本語訳は "整列" という語が当てられているようですが、なんとなく違和感があるので、ここでは "align" とのままにしています。
  4. これが良いとは思いませんが、残念ながらこういった現状があるのは確かです。

R ggsurvplotでカプランマイヤー曲線 + α (生存期間関連の表を綺麗に)

はじめに

以前の記事で、カプランマイヤーを描いてみました。

med-ruka.hatenablog.com

その後使うにつれて、もう少し綺麗に描きたいと思いました。 そこで今回はもう少し綺麗に描くための細かい技をまとめておきます。

library(survival)
library(survminer)
library(gtsummary)

前回使用したライブラリの他にgtsummary

www.danieldsjoberg.com

が追加されています。こちらは後半で紹介する綺麗に表を作るためのライブラリです。

カプランマイヤー曲線を少しだけ変更してみる。

とりあえず描くためのコード

とりあえず描くためにはまずはsurvfitでfitして、その結果をggsurvplotに渡します。 データは前回同様、元から使えるlungデータを使います。

fit<- survfit(Surv(time, status) ~ sex, data = lung)
ggsurvplot(fit, data = lung)

最初のプロット

これでも十分綺麗なんですが、少し細かいところをいじってみます。
ここからの変更の仕方は2種類あって、一つはggsurvplotの引数を変更する方法
もう一つはggplot2の関数を使って変更する方法です。

方法1 ggsurvplotの引数を変更する。

ggsurvplotの引数は各所に詳細に描いてあるので、そちらを参考に
https://www.rdocumentation.org/packages/survminer/versions/0.1.1/topics/ggsurvplot
ここでは割とよく変更する項目 かつ前回取り扱わなかった項目を見ていきます。

線の色を変える

これは簡単ですが、引数のpaletteを指定すると変更できます。

ggsurvplot(fit,
           data = lung,
           palette = c("red", "blue"))

color change

先にcolormapを指定しておいて、そちらを参照する形でもいいです。
16進数のカラーコードも使用可能なので、お好みの色があれば指定してください。

cmap <- c("#333333", "#FF4B00")
ggsurvplot(fit,
           data = lung,
           palette =cmap)

cmap

軸ラベルを変更する。

こちらも簡単ですが、xlab, ylabを指定すると変更できます。

ggsurvplot(fit,
           data = lung,
           palette = c("red", "blue"),
           xlab = "Time (days)",
           ylab = "Survival prob.")

label change

テーマを変更する。

ggplotでも使用するggthemeを指定できます。
あまり好みではないですがtheme_bwを使用するとこんな感じになります。

ggsurvplot(fit,
           data = lung,
           palette = c("red", "blue"),
           xlab = "Time (days)",
           ylab = "Survival prob.",
           ggtheme = theme_bw())

theme change

凡例の名前を変更する。

凡例の名前はデフォルトでは変数名 = 値 というようなものになっています。 今回のケースではsex = 1 or 2 となっていますが、これを変更することができます。
デフォルトだとわかりにくかったり、やたら長くなったりするので結構変更することが多いです。 この項目はggsurvplot内で変更した方が簡単だと思います。

ggsurvplot(fit,
           data = lung,
           palette = c("red", "blue"),
           xlab = "Time (days)",
           ylab = "Survival prob.",
           legend.labs = c("Male",
                           "Female"),
           ggtheme = theme_classic2())

legend change

方法2 ggplot2の関数を使って変更する。

ここまではggsurvplotの引数を変更してきましたが、ggplot2の関数を使って変更することもできます。 ここでちょっと工夫が必要で以下はエラーになります。

ggsurvplot(fit,
           data = lung,
           palette = c("red", "blue"),
           xlab = "Time (days)",
           ylab = "Survival prob.",
           ggtheme = theme_bw()) + 
  theme(text = element_text(size = 18)) 

Warning: Incompatible methods ("+.ggsurv", "+.gg") for "+" Error in ggsurvplot(fit, data = lung, palette = c("red", "blue"), xlab = "Time (days)", : non-numeric argument to binary operator

エラーの原因はggsurvplotはggplot2の関数ではないためです。 そのため、ggsurvplotのうちplotのみを取り出してくる必要があります。

p <- ggsurvplot(fit,
                data = lung,
                palette = c("red", "blue"),
                xlab = "Time (days)",
                ylab = "Survival prob.",
                ggtheme = theme_bw())
p$plot + 
  theme(text = element_text(size = 18)) 

change text size

この方法を使うと何がいいかというと、 (1) 普段使っているggplotの設定をそのまま使える。 (2) risk tableと切り分けて細かく設定できる。 という点があります。
(1) に関しては、そのままですが、ggplotでグラフを書いているとtheme関数などで
細かい指定をすると思います。その部分は普段コピペで使い回すことが多いですが、
それをそのままggsurvplotにも適応できるので便利かと思います。

(2) に関しては、ggsurvplotはplotとrisk tableを一緒に描画してくれますが、
それぞれの部分を切り分けて細かい設定をすることができます。 (たぶんggsurvplot内にもplotとtableを切り分けて設定する関数がありますが、 個人的にはggplotと共通しているこちらの方法で指定する方が好みです。)

意味があるかはわからないですが、plotとtableで文字サイズを変更して、 テーマも変えてみました。

p <- ggsurvplot(fit,
                data = lung,
                palette = c("red", "blue"),
                xlab = "Time (days)",
                ylab = "Survival prob.",
                risk.table = TRUE,
                risk.table.size = 0.3,
                ggtheme = theme_classic2())
p$plot <- p$plot + 
  theme(text = element_text(size = 18)) 
p$table <- p$table +
  theme_bw()+
  theme(text = element_text(size = 12))
p

change diff

 生存期間関連の表も綺麗に出してみる。

せっかくなので、生存期間関連の統計量の表も綺麗に出してみます。 冒頭で記載したgtsummaryを使用すると簡単で、生存期間中央値で作ってみるとこんな感じです。

fit %>% 
  tbl_survfit(probs = 0.5,label_header = "**Median survival (95% CI)**")

median survival

Cox比例ハザードモデルで解析してみるとして、こちらの結果を綺麗に出すならgtsummaryパッケージの
tbl_regressionを用いると良いです。

coxph <- coxph(Surv(time, status) ~ sex, data = lung)
coxph %>% tbl_regression(exponentiate = TRUE)

coxph

まとめ

書いてみると大したことはしていないですが、ちょっといじれるようになった気がします。 まだRで論文を書く機会がないので、真剣度が足りていないかもしれません。 また生存時間解析の解析部分については自分もまだまだ勉強不足でよくわかっていないので、ひとまずカプランマイヤーを描いてみるくらいしかできていません。 これもまた論文を書いていないことによる弊害な気がします。 早く論文を書かないと。。。

RStudio + Githubで詰まった話

はじめに

この記事は初心者がRStuido + GitHubで詰まった話です。 誰かのためになればと思って残しておきます。

筆者のレベル

普段はpythonで解析をしていて、Git + GitHubは便利に使えていました。
ただ使うと言っても個人的に使っているだけで、よくわかっておらず分からないことがあるたびにgoogleで調べるという感じでした。
pythonはPyCharmというIDEを使って書いていて、Git連携が済んでいるので、普段使いにはほとんど困っていませんでした。

背景

最近Rをよく使うようになってきましたが、RStudioとGit, GitHubが連携できるという話を聞いたので、これを機に連携して、RStudioでもバージョン管理をしよう と思いました。そこで検索で上位に出てくるページで人気のものを参考にやってみました。

qiita.com 当初参考にさせていただいたサイト

起きた問題と考察

問題1: Gitに何も出てこない問題

New projectからmake git repositoryとして、RStudio自体にはGit paneが出ているのに、中身が何も表示されませんでした。

本来はこう表示される
ここでいう
.gitignore
などの部分がまっさらでした。
試しに適当にスクリプトを書いてみても、まっさらなままでした。
ターミナルでgitを動かしてみた感じ、そもそも更新されたファイルがないという認識になっているようで、これはおかしいと思いました。
ちなみにGitHubとの連携はやってはいてGithub copilotは普通に動いておりました。
この時点では原因が皆目検討つかず
(この辺りで一度Githubにユーザー名とパスワードでアクセスしようとして、そんな古い方法は受け付けていない! と怒られる事件もあった気がしますが、 あまり覚えていません。もしかしたら解決したのはこの辺りの操作だったのかもしれません。)

解決策

そんな最中、以下のページを見つけました。
happygitwithr.com RStudioとGit, GitHubの連携の解説をしてくれているサイトです。 ここで、15章に「RStudioとGit, Githubを連携する際には先にリモートレポジトリ作っておけよ」と書いてあります。
初心者の私はこれに従うことにしました。

問題2: 今度はSSH接続で怒られる。

RStudioのNew projectからVersion controlを選べば、リモートから引っ張ってこれるようでしたので、やってみました。 適当にリモートを作っておいて, SSHで接続しようとすると、

WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! 怒られました。

SSH keyを更新する。

幸いどの問題にも先達はいるもので、

zenn.dev

という記事がありまして、こちらで修正したところ、なんとか接続可能でした。
めでたくリモートを引っ張ってくると、gitのpaneにちゃんと更新したファイルがあり、きちんとコミットプッシュができました。
みなさん大変わかりやすい記事ありがとうございます。

次回以降のプロジェクトでは最初からgit使えました。

タイトルの通り、一度リモートと接続するとどうもよく分からないのですが、次回以降は最初からgitに新しいファイルが追加されるようになりました。
めでたく最初のQiitaの記事に戻ることができ、RStudioでGit Githubが使えるようになりました。

総括

結局何が悪かったのかは実はわかりません。
記憶の曖昧なGithubアカウントをいじった部分でSSH Keyもいじっていて、その部分でもおかしなことがあった気がするので実はそこがCriticalだったのかもしれません。 結局ふわっとした理解のままGItとかをいじっているのが問題なんだろうなと思いつつ、勉強する時間は取れていないんです。

それとインターネットの情報はやはり資産だなーと思いました。 適当な記事も多いですが、よくぞこの記事を残してくれたなぁという記事に出会うことも多くて、大変助かっています。 ChatGPTにも聞いてみたんですが、あまりいい感じの答えを返してくれなかったので、検索もまだまだ大事ですね。

ダークモードで使用しているPyCharmでMarkdownをPDFに変換すると文字色が薄くなる問題とその対策

はじめに

普段いろんな目的でPyCharmを使っています。
解析コードを書いたり、ブログ記事を書いたり、いろいろなプロトコルを書いたりが主な使い道です。
VSCodeでも良かったのですが、なんとなく最初に使い始めたのがPyCharmだったため、そのまま使い続けています。
PyCharmを使ってMarkdownを書いていると、たまにpdfにして誰かに渡したり印刷したりしたくなります。
Markdownの変換自体は簡単にできるのですが、最近になって文字色が薄くなる問題が発生しました。 解決したわけではないですが、対策はできたので残します。

Markdownのpdfへの変換はこちら pleiades.io

問題

pdfを作成すると薄くなっていることです。以下のようになっています。

薄い例

対策

薄い色の設定
理由は分かりませんが、エディタのテーマカラーとカラースキームに依存するらしいです。 上記の画像の際のpdfが問題のところのpdfです。
濃い色設定
以上に変更すると
濃くなった文字
文字色が濃くなりました。 (完全な黒ではない。)

テーマカラーとカラースキームが一致すれば良いというわけではないらしく、以下の設定の場合、

ハイコントラスト設定

文字色が薄くなります。

ハイコントラスト同士でも薄い

ちなみに最も黒くなったのは (試した中では) テーマカラーをライトにして、カラースキームもライトにした場合です。

テーマ = ライト、カラースキーム = ライト

結論

ダークモードでPyCharmを使用しているとMarkdownをPdfに変換した時に文字色が薄くなる。
回避方法がありそうですが、パッと調べても出てこなかったです。。。
ダークモードを使いたければDarculaがまだマシかもしれないです。

YouTrackというところにIssueっぽいのがありましたが、これはなんなんでしょうか??

https://youtrack.jetbrains.com/issue/IJPL-92250/PyCharm-Markdown-PDF-Export-Markdown-Text-is-light-grey

StackOverflowのように解決策が示されているわけでもないので、多分まだ未解決なのでしょうかね??

データをカンマ以外で区切られたcsvファイル (?) で保存して、Excelで簡単に開けるようにする方法

はじめに

最近海外の方との共同研究の際にデータをやり取りしていて気づいたことがあります。
csvというファイル形式で普段データを扱っているのですが、comma-separated values (csv) はその名の通りデータが","で区切られています。
普段日本のPCを使う際には、csvはそのままexcelでひらけばいい感じに開けます。

しかし、どうもヨーロッパの方では小数点の区切りがピリオドではなくカンマらしいです。 小数点の入ったcsvデータをヨーロッパ方面の設定のPCで開くと変なところで区切られてしまって、まともなデータにならないということがわかりました。
そこでヨーロッパ方面の人に渡す際に、できれば無駄な動作なく簡単にexcelで開けるファイルを作りたいと思いました。

目的

csvファイルを工夫して、excelで一発で開けるようにする。*1 調べてみるとヨーロッパの方はsemicolonで区切っているようなので、そのようにしてみます。
今回はpythonでやってみます (解析の主な部分はpythonでやっているため)

データ生成

適当にデータを生成します。

import pandas as pd

df = pd.DataFrame({'red': ['apple', 'ringo'],
                   'green': ['yellow', 'purple'],
                   'water': ['ok', 'ten']})

意味はないです。このデータをcsvで書き出します。
pandasのto_csvを使えばよいです。

pandas.pydata.org

マニュアルにもあるように、sep=";"で区切り文字を変更することが可能です。ダブルクオーテーションの間に区切り文字を入れることで変更できます。 デフォルトでは","になっているわけです。*2

df.to_csv("path/test.csv", sep=';', index=False)

excelで一発で開けるようにする

ここまでで生成されたデータをexcelで開いてみると、以下のようになります。

ダメな例
Excelはデフォルトだとcsv (区切りはカンマ)以外は成形してくれません。semicolonは通常の文字列の一部として読まれてしまいます。
また調べてみるとどうもテキストファイルの先頭行に
sep=;
のように記載するとうまく読み込んでくれるとの情報がありました。
superuser.com
そこで、先ほど生成したファイルを読み込んで、先頭行に"sep=;"を書き込んで保存するというスクリプトを書いてみました。

# f_inとf_outのファイル名は変えないとsep=;のみのファイルが出来上がってしまう。
with open("path/test.csv", 'r') as f_in, open("path/testmod.csv", 'w') as f_out:
    # 先頭行に 'sep=;' を追加
    f_out.write('sep=;\n')
    # 2行目以降のデータを読み込んで書き込む
    for line in f_in:
        f_out.write(line)

これでできたtestmod.csvexcelで開いてみると、、、

うまくいった例
できました。

終わりに

なんとかできましたね。セミコロンの他、チルダや!でも行けたので、割となんでも大丈夫だと思います。
普段自分はcsvをそのまま加工するのでExcelを使っていないのですが、Excelでデータを見たりグラフをサッと作りたい人もいると思うので、誰かの役に立つこともあるかなと思いました。
個人的にヨーロッパ方面では小数点がカンマで打たれているっていうのは少しびっくりしました。
これでうまく読み込めるといいなと思います。

*1:excelを開いてからデリミタ (区切り文字) を指定して、、などは面倒だと思いました

*2:というかsemicoloで区切った場合にはcsvとは言えない気がしますが、なんて呼ぶんでしょうか?

Rグラフに注釈追加

はじめに

最近この本で勉強しています。

グラフを描画する際に、一緒に文字や直線、短径を描画したい時があると思います。
これらはデータそのものを描画しているわけではないのですが、グラフを解釈する際に有用になります。
今回はRでグラフないに文字や直線、短径を描画する方法を記載します。

目的

グラフ内に文字や直線、短径を描画する。
以下のようなグラフを作れるようになる。

使用するデータのチェック

お馴染みのiris (アヤメ) データを使用します。
データの中身を確認します。
irisデータセットはデフォルトで入っているので、一行で読み込めます。

library(tidyverse)
iris

irisデータの概観

各行が1サンプルに対応していて、Sepal.Length, Sepal.Width, Petal.Length, Petal.Width, Speciesが与えられています。1
Species (アヤメの種類) 以外は数値データです。
ここでは、Petal.Length とPetal.Widthの関係に着目してみます。 この二つに正の相関があるのではないかという仮説をもとに可視化してみることにします。

データの前処理

最初に Spealのデータは今回使用しないので、selectで最初に除去します。

dat <- iris %>% 
  select(-c(Sepal.Length, Sepal.Width))

シンプルなグラフの描画

まずは単純に描画してみます。

p <- ggplot(dat, 
            mapping = aes(x = Petal.Width,
                          y = Petal.Length,
                          color = Species)) 
p + 
  geom_point() +
  theme_classic()

シンプルにプロット

ThemeはClassicが好みです。 このグラフに、文字や直線、短径を追加してみます。

グラフ要素の追加

グラフ要素の追加はannotateを使って行います。
annotateの最初の引数には、追加する要素の種類を指定します。 - "test": 文字
- "segment": 線分
- "pointrange": 点と線分
- "rect": 四角形

文字の追加

最初は文字を追加してみます。

p + 
  geom_point() +
  theme_classic() +
  annotate("text", x = 0.5, y = 5, label = "Scatter plot", color = "black", size = 5)

文字追加

x, y は文字の位置を指定します。文字の位置は軸の値で指定しています。
その他色やサイズも指定可能です。

線分の追加

適当な線分を追加する場合にはsegmentを用います。 この線分の使い道としては、グラフ上に閾値や平均値を示すといったところでしょうか。

p + 
  geom_point() +
  annotate("segment", x = 0, xend = 3, y = 4, yend = 4, color = "gray", size = 1)+
  annotate("segment", x = 1, xend = 1, y = 0, yend = 8, color = "gray", size = 1)+
  theme_classic()

線の追加

そのまま描画すると軸が浮いてしまって不格好なので、scale_x_continuous, scale_y_continuousでexpand = c(0, 0)を指定してきちんとくっつけます。

p + 
  geom_point() +
  scale_x_continuous(expand = c(0, 0)) +
  scale_y_continuous(expand = c(0, 0)) +
  annotate("segment", x = 0, xend = 3, y = 4, yend = 4, color = "gray", size = 1)+
  annotate("segment", x = 1, xend = 1, y = 0, yend = 8, color = "gray", size = 1)+
  theme_classic()

軸を調節したグラフ

もしくは今回のデータではあまり意味がありませんが、y=xの直線を引くことで縦軸と横軸の値がどちらが大きいのかということを示すのにも使えます。

p + 
  geom_point() +
  scale_x_continuous(expand = c(0, 0)) +
  scale_y_continuous(expand = c(0, 0)) +
  annotate("segment", x = 0, xend = 3, y = 0, yend = 3, color = "gray", size = 1)+
  theme_classic()

y=xを追加

短径の追加

rectでは四角形を描画できます。
文字と合わせれば、グラフ上で注目して欲しいところを表すのに使えそうです。

p + 
  geom_point() +
  scale_x_continuous(expand = c(0, 0), limits = c(0, 2.7)) +
  scale_y_continuous(expand = c(0, 0), limits = c(0, 7.5)) +
  annotate("text", x = 0.8, y = 2.7, label = "Petal of setosa is small.", color = "black", size = 5)+ 
  annotate("rect", xmin = 0, xmax = 1, ymin = 0, ymax = 2.5, fill = "gray", alpha = 0.2)+
  theme_classic()

短径の追加

終わりに

文字や直線、短径を追加することで、グラフの解釈がしやすくなります。
今まで論文を書く際にはイラストレータなどの他のソフトウェアで注釈を追加していましたが、Rで一気にできるのであれば作業の自動化が進みそうです。
グラフのデータ点に注釈をつける際にはまた別の方法がありますので、そちらはそちらで紹介します。


  1. Sepalはがく、Petalは花びら (正確な言葉遣いではないです) をさしています。

多重比較をRで実施する方法

はじめに

Rは細々続けています。

2群以上実験や複数水準のデータを取得したとき、多重比較をしたくなることがあると思います。

総当たりの多重比較は実験デザインとしてあまりよくないとはわかっていつつも、そういう場面があることもしばしばです。ということで、今回は多重比較をRでやる方法についてです。 参考は以下のページです。(ANOVAの部分をすっ飛ばしていますが)

ANOVA in R: The Ultimate Guide - Datanovia

最初に使用するパッケージを読み込んでおきます。

library(tidyverse)
library(ggpubr)
library(rstatix)
library(gt)
library(datarium)
library(emmeans)

前提

以下の解析はtidyverseのパッケージを用いています。 実際の解析で自分のデータを使う際にはtidy dataに変換してから実行が必要です。 tidy dataについてはたくさん解説があるので、そちらを参照 (例えばこちら) 17  整然データ構造 – 私たちのR

一元配置デザインの場合

要するに3群以上の平均値について比較したい場合です。教科書的には (?) よく、ANOVAしてpost hoc みたいな言い方をしますが、別にANOVAは必須ではないと思われます。

使用するサンプルデータはplant growthで、3条件 (control, treatment1, treatment2) での植物の成長を見ています。 組み込まれているので、

data("PlantGrowth")

で読み込めます。

まずはグラフをチェックしてみます。(ggpubrだとこういうとき楽)

ggboxplot(PlantGrowth, x = "group", y = "weight", color = "group")

箱ヒゲ図

なんとなく群間に差がありそうな気がします。

Tukey-HSD

よく使うTukey-HSDはいかで実行可能です。

PlantGrowth %>% tukey_hsd(weight ~ group)

ちなみにgtパッケージを使うと以下のような整った表形式で表示可能です。

PlantGrowth %>% tukey_hsd(weight ~ group) %>% gt()

tukey

Tukey-HSDは論文でもよくみますが、独立、正規性、等分散性の3条件が必要なので、適応可能な範囲は比較的狭いと思われます。

Bonferroniとその変法たち

Bonferroniもよく見かける手法です。これも解説はいくらでもネットにあるので飛ばしますが、検定の回数で有意水準を割るというのが簡単な解説です。(逆にp-valueに検定の回数をかけて表示することも) そこから派生したHolmやShafferの方法は割る数の方を少しずつ減らしたりしている方法になります。 計算方法からわかるように、Bonferroniやその変法はかなり適応範囲が広いです。

pairwise_t_test(data=PlantGrowth,
                weight~group, pool.sd = TRUE,
                p.adjust.method = "holm") %>% gt()

ここではHolmをやってみて、gt()で表形式で出してみました。

holm

pairwise_t_testの引数は最初はデータ、2つ目はfomula形式になっています。 fomula形式は 独立変数 ~ 従属変数 という形式での記載になっております。この記載が変数間の関係性が明示的で私は好みです。 3つ目はpool.sdで、(間違っているかもしれませんが) 検定統計量を計算する際の分散としてプールされた分散を使用しているという意味だと思っております。 最後のp.adjust.methodで調整方法を選べて、ここではholmにしていますが、他にも"holm", "hochberg", "hommel", "bonferroni", "BH", "BY", "fdr", "none"が選択可能です。(あまり使わないので、bonferroniとholm以外わかりませんが)

ちなみに違いを見るためにBonferroniも出してみます。

bonf

最もp-valueが小さい比較のp-valueは同じですが、他で違いがあります。holmはp-valueを小さい順に並べて、1つずつp-valueにかける値を減らします。そのため最初に評価される、最も小さいp-valueは同一になります、

二元配置デザインの場合

二元配置の場合、多重比較をやりたくなる場面としては交互作用があって、全群比較をしたくなった場合だと思います。 ここでは上記と異なるデータで、jobsatisfactionというデータを用います。

データの見た目
データはid列とgender列、education列とscore列からなります。 まずは簡単に図示してみます。

ggboxplot(
  jobsatisfaction, x = "gender", y = "score",
  color = "education_level"
)

jobsatisfaction

今回はANOVAの結果も載せておきます。

jobsatisfaction %>% anova_test(score ~ gender * education_level) %>% gt()

2 way ANOVA

interactionが有意になっています。 この場合、全群での比較をしたいわけですが、1元配置のときのpairwise_t_testはそのままだと使えません。 一度データをグループに分けてから使用する必要があります。

jobsatisfaction %>% 
  tukey_hsd(score~education_level + gender,
                  pool.sd = TRUE,
                  p.adjust.method = "holm")

multiple comp

三元以上の場合

おそらく同様にできると思います。 ただ三元配置以上は交互作用の解釈がかなり難しくなるので、可能であれば実験デザインの段階で避けるべきでしょうね(少なくとも私がやっている生物系の実験の場合には)

感想

Rでの多重比較について書いてみました。 以前はSPSSを使っていたのですが、SPSSではGUIだったのでRとはだいぶん感覚が違います。Rの方が手法を勉強する気にはなるので良いと思っております。