StatsBeginner: 初学者の統計学習ノート

統計学およびR、Pythonでのプログラミングの勉強の過程をメモっていくノート。たまにMacの話題。

頻度主義の「統計的仮説検定」についての理解度チェック

うちは工学部で、4回生になると研究室に配属されて卒論のための研究が始まるのですが、その段階で統計データの分析をしなければならないので、研究室内で毎年春から夏にかけて、統計学の基本的な理論とRのコードの書き方の勉強会が行われています。
で、この勉強会の1回目あたりで扱う「統計的仮説検定」についての理解(本質的な意味が分かっているかどうか。まぁ私も分かっているかどうか怪しいですが。)をチェックするための質問をいくつか考えているのですが、他にもっと良いものありますかね?どうでしょう。

  1. サンプル数とサンプルサイズの違いは?*1
  2. t分布とF分布について、どのような確率分布であるか、「グラフ」と「(α値付きの)分布表」をどちらも使ってみんなに説明できますか?α値とp値の関係が分かることと、両側確率、片側確率の違いがわかることが必須です。*2
  3. t分布が発見されたことによって、われわれは何ができるようになった?ハンドルネーム「student」氏はどこがすごい?
  4. 自由度って何が自由なん?*3それ計算して何が嬉しいん?*4
  5. p値って何の確率なのか、正確に説明できますか?*5
  6. 95%信頼区間って、何が95%なん?*6
  7. 統計的仮説検定でp値が小さくなりやすく(有意になりやすく)なるのはどういう時?ついでに検出力の話もできれば完璧。
  8. 不偏推定量と一致推定量の違いは?
  9. p<.05が有意水準としてひろく採用されているのはなぜか知ってる?*7
  10. 平均値と標準偏差が報告されている基本統計表をよく見るけど、標準偏差からは何が分かる?*8

*1:しょうもないけど、母集団からサンプリングするというイメージを確認するため。

*2:ソフトの使い方の解説を重視した簡単なテキストだと、分布表とか載ってないことがあるので、これをやらせるのは意外と大事。

*3:これは答えやすい

*4:これは答えにくい

*5:教員でもつい「帰無仮説が真である確率」みたいな間違ったことを言ってしまう人が意外といます。

*6:ベイズ信用区間みたいな捉え方をしていないかの確認。

*7:内容的にはただのウンチクですが(笑)、ここに疑問を持たない学生は物事を本質的に理解しようとする態度が足りないと思うので、案外重要。

*8:いろいろ言えるけど、標準偏差を上下に2個分でだいたい95%になり〜みたいな話ができるといい。

不偏分散の分母がn-1である理由の直観的な理解

統計学初学者の超あるあるネタなので、くどくど説明しませんが、件名の通りです。
で、これをChatGPTに説明させてみたら、概ね以下のような趣旨の説明をしていて、けっこう腑に落ちる感じがしました。

データポイントが n 個あるとき、それを「n次元空間」に配置して考えることができる。ただし、「平均で中心化されたデータ」は、1次元少ない空間上に存在することになる。したがって、そのデータのばらつき(広がり)を評価する際は、n−1 次元上でのばらつきを計算すべき。

注意が必要なのは、普通はたとえば1000人の学生の体重のデータが得られた場合、体重という1つの(1次元の)変数に関して1000個のサンプルが得られたというふうに捉えるけど、ここでは1000個の(1000次元の)変数に関して1つずつ値が得られたというふうに捉え直している点。
1000次元空間上の1点に今回得られたデータセットが配置されるというイメージだと、「1つの点のばらつきって??」となって混乱しそうなところがトリッキーではある。
1000次元の空間上の1点として1つのデータセットが表現されることになるわけだけど、そのデータセットが、xy平面でいうところのy=x(いわゆる45度線)からどれだけズレているかで、データのばらつきを表す発想だと言い換えてもいいと思う。*1
「平均で中心化された結果として、1次元小さい空間にばらついている」というのも、具体的にイメージするのが難しく、次元というよりはランクと言ったほうがいいのかも。


【追記】
あとで考えたのだが、1回のサンプリングで得られたデータがn-1次元空間にばらつくのではなく、サンプリングを何回も繰り返したときに、そのサンプルのばらつきがn-1次元空間に収まるということで、これは図示すればわかりやすい。
サンプルサイズ3のデータをサンプリングして、3次元空間に描画するというのをランダムにやると、当然立体的にまんべんなく分布するのだが、平均や合計が特定の値になるように制約を置くと、平べったく平面上に分布する。
平均値を固定して何回もサンプリングするという話と、今回たまたま得られたデータの平均値を「正」として分散を計算するという話のつながりを意識しないといけないので、頭がこんがらがってくるが。

*1:n次元の空間にランダムに生成されたデータの45度線からの距離は、nの平方根に比例し、次元が大きいほどばらつきは大きくなる。

DeepSeekが深読みしすぎていて面白い

推論能力(多段階のロジックなどが必要な場合でも正しい答えを出す能力)の高さを謳っている最近のLLMは、ユーザーの質問に対してすぐに回答を返すのではなく、いったん生成した回答について内面的な反省を繰り返してから、ユーザーに返答するようになっていると言われます。OpenAIのo1などはそのプロセスを伏せているのですが、DeepSeekはその部分も「内なるつぶやき」みたいな感じで全て可視化してくれているので、見ていて面白いです。



たとえばさっき、「以下の数列の次の値を予想してください。全て日本語で考えてください。」という質問を打ち込んで、具体的にどのような数列なのか与えるのを忘れてしまいました。
そしたらDeepSeekが悩みすぎて、1分半ぐらいかけて、以下のようにめちゃめちゃ長い「内なるつぶやき」が出てきました。
「ただし、もしかしたらトリックのような問題かもしれません。」から、異常な深読みが始まります。

では、問題を解くために、まずは与えられた数列のパターンを見つけ出そうと思います。でも、ここで一つ問題があります。ユーザーが具体的な数列を提示していないんです。これでは通常、次の項を予測することはできません。

もしかしたら、ユーザーは質問の入力時に数列の数値を書き忘れたのかもしれません。例えば、「1, 3, 5, 7, 9...」のような数列を想定していた可能性があります。あるいは、別のパターンがある数列かもしれません。でも、具体的な数字がないので、どうしようもありません。

まず、ユーザーに数列の具体的な数値を確認する必要があるでしょう。しかし、現在の状況では、ユーザーが日本語で考えて回答するように指示しているので、その流れに沿って対応しなければなりません。

考えられる可能性として、ユーザーが質問の際に数列を入力し忘れたということがあります。あるいは、以前の会話で既に数列が提示されていて、その続きを尋ねているのかもしれません。しかし、この会話履歴を見る限り、そのような情報はありません。

そこで、このような場合の対処法として、一般的な数列の例を挙げて説明するか、あるいはユーザーに数列の具体的な数値を尋ねる必要があります。しかし、ユーザーは「以下の数列」と言っているので、おそらく何らかの数列が存在するはずです。しかし、実際のメッセージには数列が記載されていません。これは入力ミスの可能性が高いです。

ただし、もしかしたらトリックのような問題かもしれません。例えば、「以下の数列」という言葉自体が問題の一部で、例えば「以下」という言葉に関連する数列を考えさせるのかもしれません。例えば、「以下」の文字数を数えると、「以下」は2文字なので、2から始まる数列かもしれません。しかし、これはかなり強引な解釈です。

あるいは、問題文自体が数列の一部になっている可能性もあります。例えば、問題文の文字数や単語数が数列を形成しているかもしれません。問題文を日本語で数えると、「以下の数列の次の値を予想してください。全て日本語で考えてください。」という文章の文字数は、全角文字として数えると、それぞれの文字数はどうなるでしょうか?

まず、問題文を分解してみます。「以下の数列の次の値を予想してください。全て日本語で考えてください。」という文の文字数を数えます。句読点も含めると、以下のようになります:

「以」1

「下」2

「の」3

「数」4

「列」5

「の」6

「次」7

「の」8

「値」9

「を」10

「予」11

「想」12

「し」13

「て」14

「く」15

「だ」16

「さ」17

「い」18

「。」19

「全」20

「て」21

「日」22

「本」23

「語」24

「で」25

「考」26

「え」27

「て」28

「く」29

「だ」30

「さ」31

「い」32

「。」33

つまり、33文字です。しかし、この文字数が数列に関連しているとは思えません。あるいは、単語数かもしれません。「以下の 数列 の 次の 値 を 予想 して ください 。 全て 日本語 で 考え て ください 。」で単語数は15程度ですが、これもパターンを見つけるのは難しいです。

他に考えられるのは、問題文が何かのジョークや言葉遊びである可能性です。例えば、「以下の数列」という部分に注目して、「以下」の「以」の文字コードや画数を使うなどです。「以」の画数は4画、「下」は3画、合わせて7画かもしれませんが、これも推測の域を出ません。

もしくは、ユーザーが何らかのエラーで数列を入力できていない状況です。その場合、回答として数列の不足を指摘する必要がありますが、ユーザーは日本語で考えるように指示しているので、その点を考慮しつつ、丁寧に数列の提示を求めるべきかもしれません。

しかし、現在の状況では、ユーザーが実際に数列を提示していないため、適切な回答ができません。通常、このような場合は「数列が記載されていませんので、具体的な数値を教えてください」と返答するのが適切です。しかし、ユーザーは「全て日本語で考えてください」と指定しているので、その指示に従いながら、問題の不備を指摘する必要があります。

ただし、この問題がテストやクイズとして出題されている場合、例えば「以下の数列」という言葉自体が数列の一部で、言葉の文字数や何らかの規則性がある可能性もゼロではありません。例えば、「以下の数列」の各文字の画数を見てみます:

「以」:4画

「下」:3画

「の」:3画

「数」:13画

「列」:6画

これらを並べると、4, 3, 3, 13, 6 という数列になります。この次の数値を予想する問題かもしれません。しかし、この数列の規則性を見つける必要があります。

4, 3, 3, 13, 6...

この数列のパターンを見つけようとします。まず、4から3へは-1、3から3は±0、3から13は+10、13から6は-7。この差の数列は -1, 0, +10, -7 で、特に規則性が見えません。

あるいは、各数字自体に何か規則があるか。4,3,3,13,6...

4 +3=7, 3+3=6, 3+13=16, 13+6=19… これも不明。

別の角度から、これらの数字を何かのコードと見るか、例えば、画数を別の方法で数える(異なる字体の場合)可能性もありますが、一般的な画数では上記の通りです。

もしくは、各文字の部首の画数など、別の要素を考慮しているかもしれませんが、これも推測が難しいです。

さらに、「以下の数列」という言葉のローマ字の文字数や、何らかの変換を試みることも考えられますが、これも根拠に欠けます。

このように、問題文に数列が提示されていないため、推測が非常に困難です。ユーザーが何らかのミスで数列を入力し忘れたと考えるのが自然ですが、もしこれが意図的な問題であるなら、非常にユニークなケースと言えます。

いずれにせよ、正確な回答のためには数列の具体的な数値が必要です。しかし、現状ではそれがないため、問題の不備を指摘するか、あるいはユーザーが想定していたかもしれない一般的な数列の例を挙げて説明するしかありません。

例えば、典型的な数列問題として、等差数列(2,4,6,8...なら次は10)、等比数列(3,9,27...なら次は81)、階差数列(1,3,6,10...なら次は15)などが考えられます。あるいはフィボナッチ数列(1,1,2,3,5...なら次は8)などです。

しかし、ユーザーが具体的にどの数列を指しているのか不明なため、これらの中から適当に選ぶことはできません。また、問題文の冒頭に「以下の数列」とあるため、何らかの数列が存在するはずですが、実際には記載されていません。

この状況を踏まえると、おそらくユーザーは質問の入力時に数列を添付するのを忘れたか、テキストのコピーでミスが発生した可能性が高いです。したがって、適切な対応としては、数列の提示を求めることが最善です。

ただし、この思考プロセスを日本語で記述することが求められているため、このように問題の不備を指摘しつつ、可能な限り推測を試みるプロセスを説明します。

結論として、数列が提示されていないため正確な回答は不可能ですが、もし典型的な数列を例示するなら、その一例を挙げることはできます。例えば、等差数列の場合、「2, 4, 6, 8...」の次は「10」と予想されます。しかし、これがユーザーの意図した数列かは不明です。

以上のことから、問題文に数列が記載されていないため、次の値を予想できないことを指摘し、数列の入力を求めることが適切だと考えます。


ここまで悩んだ上で、最終的に、

問題文に数列が記載されていないため、次の値を予想できません。数列の具体的な数値をご提供いただければ、そのパターンを分析し次の値を予想いたします。例えば、「2, 4, 6, 8...」のような等差数列であれば次は「10」、「1, 1, 2, 3, 5...」のようなフィボナッチ数列であれば次は「8」と推測されますが、実際の数列がないと正確な回答は不可能です。数列の入力をお願いいたします。


という答えを出してきました。
考えすぎやろw

重み付き回帰で2種類の重みを使いたい時

Rで重み付きの回帰を実行する場合、lmとかlmer(ランダム効果を使う場合)のweights引数に重みを指定すればいいだけですが、2種類の重みを同時に使いたいような場合があります。たとえばパネル調査データで、母集団からのサンプリングのバイアスを補正するための重みと、初回調査から脱落しやすい属性の人たちが過小評価されてしまうのを補正するための重みとか。
考え方はいろいろあると思いますが、とりあえずは2つの重みの積をweightsに指定しておけばいいんじゃないかと思います。
というのも、下記のコードで確認しているように、重み付き回帰を実行するとはすなわち、重みの平方根を元の変数にかけてからふつうに回帰をやるのと同じ(切片項や、ランダム効果の分散にも重みを反映する必要がある点に注意)なので。いったん変数変換で重みを反映したデータにべつの重みを上からかけ直すのと、重み同士をあらかじめ乗じてからかけ直すのは同じ意味になります。
まぁでも、重みの合成の仕方も、データの性質によって考え方がいろいろありそうな気はしますが(重みの平均をとるとか)、勉強してないのでわかりません。積を取る方法だと、重みが極端な値になって扱いづらいことがあるようです。


以下のコードは、weights引数を使う場合と、あらかじめ変数に重みの平方根をかけてから回帰する場合の比較です。(2種類の重みの反映の例ではないです。)

> library(dplyr)
> library(lme4)
> 
> ### lm関数での重み付き回帰の原理の確認
> 
> # 変数を作成する
> set.seed(1)
> x1 <- rnorm(mean=0, sd=3, n=100)
> x2 <- rnorm(mean=3, sd=1, n=100)
> y <- 1 + 2*x1 + 3*x2 + rnorm(mean=0, sd=3, n=100)
> 
> # 重みをつくる
> w <- rexp(n=100)  # 重み変数
> 
> # 重みの平方根
> root_w <- sqrt(w)
> 
> # 重みなしの回帰
> lm(y ~ x1 + x2) %>%
+   coef()
(Intercept)          x1          x2 
   1.557262    2.021110    2.839600 
> 
> # 重み付き回帰(重み付き最小二乗法)
> lm(y ~ x1 + x2, weights=w) %>%
+   coef()
(Intercept)          x1          x2 
   2.839395    1.938573    2.345262 
> 
> # 重みの平方根をかけてから回帰
> yw <- y * root_w
> x1w <- x1 * root_w
> x2w <- x2 * root_w
> 
> # 切片を0で消して、重みの平方根を切片とする
> lm(yw ~ 0 + root_w + x1w + x2w) %>%
+   coef()
  root_w      x1w      x2w 
2.839395 1.938573 2.345262 
> 
> ### lmer関数での重み付き回帰の原理の確認
> 
> # 変数を作成する
> set.seed(1)
> group_id <- factor(rep(1:100, 10))
> group_effects <- rep(rnorm(n=100, mean=0, sd=3),10)
> x <- rnorm(mean=1, sd=5, n=1000)
> y <- 2 + 3*x + group_effects + rnorm(mean=0, sd=3, n=1000)
> 
> # 重みと重みの平方根
> w <- rexp(n=1000)  # 重み変数
> root_w <- sqrt(w)
> 
> # 重みなしの回帰
> lmer(y ~ x + (1 | group_id)) %>%
+   fixef()
(Intercept)           x 
   2.230189    3.009346 
> 
> # 変数に重みの平方根をかける
> yw <- y * root_w
> xw <- x * root_w
> 
> # モデル1:weights引数を使用
> lmer(y ~ x + (1 | group_id), weights=w) %>%
+   fixef()
(Intercept)           x 
   2.033027    2.998279 
> 
> # モデル2:重みの平方根をかけた変数でふつうに回帰
> # まず、切片を0でいたん消去して、root_wを新たな切片として取り入れる
> # ランダム効果については、分散を重みによってスケーリングする。
> # ランダム効果の記法のうち|の前の部分はもともと、分散を表しているので、
> # そこに重みの平方根を入れればよい。
> # グループidにかけるわけではない。
> lmer(yw ~ 0 + root_w + xw + (root_w | group_id)) %>%
+   fixef()
  root_w       xw 
2.033027 2.998279 


ちなみに、lmにweightsを指定した場合は重み付き最小二乗法をやっていて、重み付き二乗誤差を最小化するようになってるはずですが、lmerはデフォルトでは制限付き最尤法(REML)なのでちょっとややこしいです。

Mamba(最近話題の新しいAIアーキテクチャ)の名前の由来

長大なシーケンスを扱うことができ、処理速度が高速で、Transformerを超えるのではないか(今のところケースによって強み弱みはあるらしいけど)と話題になっているMamba(論文リンク)ですが、名前が何の略だったか思い出せなくて確認していたら、そもそも何かの略ではなく、ヘビの名前だそうです。
先日、飲み会中に「ヘビの名前ですか?」と訊かれて「いや、何かの略だったかと」と答えたのですが、ヘビの名前でした。以下は開発者のツイートです。



最後のS4というのは、Mambaのベースとなっている状態空間モデルのStructured State Spaces for Sequence Modelingのことですが、Sの字が蛇みたいに見えるということかな。
で、ブラックマンバというヘビは移動速度がめちゃめちゃ速くて、かつ毒が強力で殺傷能力が高いらしいので、長めの系列データ(シーケンス)がすごい速さでモデルの中を移動する様子や、問題をあっさり片付けてしまう様子をイメージして、このヘビの名前を付けたようです。
Transformerなどの既存モデルを殺してしまうというイメージもあるのかなと思いました(上記ツイートのスレッドの最初を見ると、ヘビがロボットに襲いかかるイラストが添付されている)。

LightGBMにおけるカテゴリ変数の扱い方

LightGBMにおけるカテゴリ変数の扱いについてググっていたら、以下のような投稿があった。
[SOLVED] How exactly does LightGBM handle the categorical features? | Kaggle


この投稿のなかで、いろいろ参考になるリファレンスが貼られている。

max_cat_to_onehot (default = 4, type = int, constraints: max_cat_to_onehot > 0)
when number of categories of one feature smaller than or equal to max_cat_to_onehot, one-vs-other split algorithm will be used
Parameters — LightGBM 4.2.0.99 documentation

LightGBM offers good accuracy with integer-encoded categorical features. LightGBM applies Fisher (1958) to find the optimal split over categories as described here. This often performs better than one-hot encoding.
Advanced Topics — LightGBM 4.2.0.99 documentation

Optimal Split for Categorical Features
It is common to represent categorical features with one-hot encoding, but this approach is suboptimal for tree learners. Particularly for high-cardinality categorical features, a tree built on one-hot features tends to be unbalanced and needs to grow very deep to achieve good accuracy.

Instead of one-hot encoding, the optimal solution is to split on a categorical feature by partitioning its categories into 2 subsets. If the feature has k categories, there are 2^(k-1) - 1 possible partitions. But there is an efficient solution for regression trees[8]. It needs about O(k * log(k)) to find the optimal partition.

The basic idea is to sort the categories according to the training objective at each split. More specifically, LightGBM sorts the histogram (for a categorical feature) according to its accumulated values (sum_gradient / sum_hessian) and then finds the best split on the sorted histogram.
Features — LightGBM 4.2.0.99 documentation


ちなみにFisher(1958)は、斜め読みしかしてないので雰囲気しか把握してないが、1次元の連続値データに基づいてサンプルをグループ分けする際に各グループ内で分散が小さくなるようにするにはどうすればいいかを議論していて、例として200個の数字を10のグループに分ける場合の話がされていた。


ところで、上記投稿者の人自身は、結論を間違えているような気がするな。
この人は、「カテゴリ数(水準数)が`max_cat_to_onehot`以下の場合は、one-vs-other split algorithmが採用され、それより多い場合は、one-hotエンコーディングが採用される」と理解したようなのだが、そもそも水準数が少なければone-hotでいいはずなので、水準数が閾値以下であればone-hot encodingされると理解すべきだろう。


上記3つめの引用に書いてあるように、もともとの問題意識は、カテゴリ数が多い(high-cardinalityな)場合にone-hotエンコーディングをしようとすると非常にアンバランスな木ができてしまい、最適な分割結果に近づくために木を深くしなければならなくなるので、大雑把な分割の中からいいものを選びたいという話である。で、one-vs-other algorythmと呼ばれているものは、one-vs-restと言うことの方が多いようだが、これはようするにone-hotとほぼ同じ意味だと思う。つまり、多クラスの分類を、「あるクラスとそれ以外」に分ける分類器を複数組み合わせて扱うということ。


↓この投稿で検証されているように、`max_cat_to_onehot`よりも少ない水準数の場合は、one-hot encodingした場合と結果が同じになるようだ。
https://stackoverflow.com/questions/65773372/lightgbm-splits-differently-on-the-same-dataset-one-hot-encoded-vs-one-vs-other
(この投稿は、「同じになると思ったらならないんだけど…」という内容で始まって、「あ、バージョン上げたら同じになったわ」と自己解決しているもの。)


で、じゃあカテゴリ数が多い場合にどうなってるのかなのだが、まずそもそも、以下のような「ヒストグラムベース」の木構築の話を頭に入れておかないといけない。


勾配ブースティング決定木ってなんぞや #Python - Qiita
xgboostのコードリーディング(その2) - threecourse’s blog
Kaggleで大人気!勾配ブースティング決定木の軽量モデル「LightGBM」を詳細解説! | DeepSquare


勾配ブースティング決定木においては、ある時点で構築された木に基づく予測値と実際の値から誤差を計算して、誤差関数を「予測値で」偏微分する。つまり、予測値をどっちに動かすと、誤差がどれだけ増えるかを計算する。
ニューラルネットのように、「モデルのパラメータで」偏微分するわけではないので注意が必要。
各ノード(決定木の分岐点)において、特徴空間のどの部分を分割すると、サンプルの勾配の合計が最大限に減少するかを学習していく流れになる。
で、この際、分割点の探索は計算コストが高いので、連続変数ならビンでまとめてヒストグラムのようにした上で、どのビン間で分割するのがよいかを探索することになる。カテゴリ変数(ラベル)の場合は、数量化の工夫を行う必要があって、`(sum_gradient / sum_hessian)`を使ってどうたらこうたらというのは、その話だ。


で、決定木系のアルゴリズムをほとんど勉強してないので単なる想像なのだが、LightGBMにおいて水準数の多いカテゴリ変数扱いは、たぶん以下のような感じなのかな?
全く自信はないのであとで時間あるときに確認しようと思ってるのだけど、誤解だとしても「現時点でどう理解したか」メモっておくのが効率的なので、メモっとくことにするw

  1. 現時点の木の構築:初期の木、またはブースティングの反復を経た現在の木が与えられる。
  2. 全サンプルの勾配とヘシアンの計算:各サンプルについて、勾配(損失関数を予測値で一次偏微分)とヘシアン(損失関数を予測値で二次偏微分)を計算する。
  3. カテゴリ変数を数値に変換:各カテゴリについて、そこに属する属するサンプルの勾配/ヘシアン比の合計値を計算する。この値は、そのカテゴリの重要性を示す。
  4. カテゴリのソート:カテゴリ変数を、上記の重要度に基づいてソートする。
  5. ヒストグラム化:ビンでまとめてヒストグラム化する。
  6. 最適な分割点の探索:Fisher (1958)の手法や他の統計的手法を用いて、どのビン間で分割するのが最も効果的かを探索する。(どう分割すれば勾配をさらに小さくできるかを探索する。)


カテゴリ数が閾値(デフォルトでは4)以下であれば、ふつうにone-hotエンコーディングが採用される。それより大きい場合は、上記のような(誤解ならこれを修正した)手順にしたがって、分割される。なおたぶん、`max_cat_threshold`というパラメータ(デフォルトは32)に設定した値より細かくは分割されないということだと思う。決定木は通常、バイナリ分割(2つに分けていく)で、それを何回か重ねて分けていくが、32分類以上にはしないということだろう。

MacでのChrome AppsのTwitter (X)アプリのインストール

今、Macでツイッター(X)のアカウントを複数運用するなら、Chrome Appsのアプリが最適だと思っていて、それを再度インストール(正確にはMacの別のユーザアカウントにインストール)しようと思ったら方法が分からなくて困ったのでメモしておきます。
説明するほどの手順ではないんですが、ぐぐっても出てこなかったので。
 

  1. ChromeでTwitter(X)にログインする
  2. Chromeの右上の「…」を縦にしたボタンのメニューに「Xをインストール」っていう選択肢があるのでそれをクリック
  3. ローンチパッド上にXのアプリのアイコンが出るのでそれをドックに配置

 
で完了です。
`~/Applications/Chrome Apps/X.app`
という場所に入っています。
ユーザディレクトリの配下なので、
`/Applications`
の中を探しても無いので気をつけてください。
 
ちなみに私は、もともとインストールしていたユーザアカウントではTwitter.appになってて、新しくインストールしたやつはX.appになってます。
 
あとは余談ですが、自分はMac用のツイッタークライアント使用歴は以下のような感じです。

  1. Janetter:一瞬話題になってから何となく使ってたやつで、夜フクロウとかTweetDeckに比べて何がよかったかは忘れた。
  2. Twitter for Mac(公式):結局公式がいちばん使いやすい気がしてしばらく使っていたが、開発会社が無くなったか何かで、2018年に廃止された。2019年に一時的に復活してるしいけどいまいち覚えてない。
  3. Catalyst版:Catalystという、iPad用アプリをMacで使えるようにするフレームワーク(?)によって、Twitter for Mac(公式)が復活。しばらく使ったが、廃止されたか不具合が気になったかで、使うのをやめた。
  4. Multiple Twitter Accounts Chrome(Chrome拡張):Chrome拡張でTwitterアカウントを複数運用するツールがあり、これをつかってブラウザでみるようになったが、イーロン・マスクに買収された後に何かが起きてまともに使えなくなった。(2023年1月のサードパーティ排除騒動かな?)
  5. Chrome Apps版:経緯は忘れたけどChrome Appsのアプリを発見してインストール。現在に至る。