タグ別アーカイブ: 分析

Slackを用いた授業外ライティング活動の便利ワザ[Google Spreadsheet編②]

はじめに

下記の前回の記事の続きです。

Slackを用いた授業外ライティング活動の便利ワザ [Google Spreadsheet編①]

前回の記事では,Google Apps Script (GAS)を利用して,Slackに書き込まれた内容をGoogle Spreadsheetに記録し,その記録をもとに語数を数えるという話でした。この記事では,それをもう少し便利にするために,「ある特定の期間で区切って語数をカウントする」というお話です。例えば,1日ごと,1週間ごとみたいな。月ごとの場合は,前回の記事で紹介した方法は月ごとに新しいspreadsheetのファイルができるので楽なんですが,「同じファイル内のシートの中で一定の期間の間ごとに区切りたい」となると,少しだけ工夫がいります。といっても,spreadsheet内で関数を書くだけなのでそんなに大変なことではありません。一度雛形ができればあとはコピペして少し書き換えるだけですみます。

前回のおさらい

少しだけ前回の記事に書いた内容に触れておきます。GASを使ってspreadsheetに取り込まれた状態は,下記画像のようになっています。

Screen Shot 2018-09-24 at 22.50.58

A列が日付,B列が名前,C列が書き込まれたテキスト,D列がもとのJSON形式の情報です。このシートが,チャンネルの数だけ一つのファイルにある状態です。前回は,GASで更新されるこのシートの情報を以下のような手順に沿って使いました。

  1. importrange関数を使って別のファイルにエクスポート
  2. そのシートに語数をカウントするE列を挿入
  3. sumproduct関数で名前列を参照してE列の合計を求める

すると,下記画像のようなまとめシートができます。

sumproduct-example

今回は,そこに「日付の情報も参照する」という情報も付け加えていくことになります。

同じ月内で1週間ごとに語数を数える

では,実際に私がやっている実践に即して,1週間ごとに語数を数えるという作業をやる方法です。先程説明したように,前提は一ヶ月で一つのファイルができ,そのファイルの中にチャンネルの数だけシートがあるという状態です。同じ月内であれば一つのシートの情報で,以下のような作業をspreadsheetにやってもらいます(注1)。

  1. まとめシートのA列にあるセルの名前に当てはまるものだけをフィルタリング
  2. 参照元シートのB列にある日付の情報を参照し,指定した1週間のものだけをフィルタリング
  3. 1と2で絞り込まれたデータのE列の語数を合計して計算する

注意点は,参照するデータのシートでは名前がB列,日付がA列なのに,まとめシートでは名前はA列になっているという点です。どの列にどのデータがあり,どこの列を参照するのかを考えないと混乱するかもしれません。さて,前回はsumproduct関数使ったんですが,今回使うsumifs関数でも同じことが可能だと思うので,全部sumifs関数でやってもいいかもしれません。

先にあとで説明する作業が完了した状態のシートの画像を見てください。1つ前の画像の状態から,下記の画像の状態にすることが今回の目標です(注2)。

Screen Shot 2018-10-12 at 14.12.11

私がこのSlackライティング活動を採用しているのは金曜日の授業で,金曜日から次の週の木曜日までを1週間としています。日付で区切っていて時間で区切っているわけではないので,今回は日付で絞り込みにしています(午後5時までとかそういう切り方だと時間も指定する必要があります)。

では,上の画像のB2の列にはどのような式が入力されているのか見てみましょう。

Screen Shot 2018-10-12 at 14.12.38

画像中の式で最後に”-5″しているのは,最初は必ず”@XXX has joined the channel. “という5語の文が記録されているので,それを排除するためです。

sumifs関数の基本的な引数はsumifs(合計範囲, 条件範囲1, 条件1, 条件範囲2, 条件2, …)のようになっています。合計範囲とは,合計を計算したい範囲のことですので,今回の場合はE列の語数の列を選択します。”Friday2_201809″というのが,金曜2限クラスの9月分のデータがエクスポートされたシートになります。このまとめシートはimportrangeで自動的にデータがエクスポートされるように設定しているspreadsheetファイルの中に作っているので,”Friday2_201809!$E$1:$E$1000″で,「9月分のデータのE列(語数列)」を合計範囲として指定していることになります。コピーしたときに範囲が変わることを防ぐために,絶対参照にしています。

2つ目の引数は,同じ”Friday2_201809″のA列を1つ目の条件範囲にしています。A列は日付の入っている列ですから,日付列を条件範囲としていることになります。そして,3つめの引数で日付の条件指定を行います。最初の1週間は,もともとデータ記録の一番最初(2018年9月21日)が1週間の1日目でしたので,7日目(終わりの日)だけを指定しています。これには等号・不等号の使い方でいろいろなやり方が考えられますので,この例は一例です。

“<“&”2018-09-28”

等号・不等号は必ずそれのみをクォーテーションマークでくくり,クォーテーションマークでくくった日付と&でつなぐようにします。上の式が意味しているのは,「2018年9月28日より前(つまり27日以前)」です。これで,9月28日0時00分以降の書き込みが排除されます。

4つ目の引数は条件範囲2です。”Friday2_201809″のB列を参照しています。B列は名前の入った列ですので,名前の絞り込みをしようというわけです。最後の引数は$A2です。「条件範囲2(元データの名前列)の中で,まとめシートのA2セルの名前に当てはまるものだけをフィルタリングしてね」ということです。指定する順番自体は前後していますが,上にも挙げた以下の3つの作業が1つの関数で実現されたことになります。

  1. まとめシートのA列にあるセルの名前に当てはまるものだけをフィルタリング
  2. 元ファイルのB列にある日付の情報を参照し,指定した1週間のものだけをフィルタリング
  3. 1と2で絞り込まれたデータのE列の語数を合計して計算する

これであとは,列を横に足して,別の日付指定をすれば,あとはその条件に当てはまるものだけが自動的に記録されていくことになります。合計範囲指定は固定ですが、条件範囲と条件の指定は名前が先で日付が後でも構いません。

1週間のはじめとおわりを指定する場合

さて,上のやり方は,1週間の終わりだけの指定でしたが,それが使えるのは最初の1度だけで,次からは1週間のはじめの日も指定する必要があります。「○月○日から○月○日まで」としたいわけです。これをやるには,sumifs関数の条件範囲と条件を1つずつ追加すればいいだけです。画像上では順番が前後しますが,下の画像のD列(10/5-10/11)の1週間を指定した場合を見てみましょう。

Screen Shot 2018-10-12 at 14.13.34

1つ目の引数は同じですが,2番目と3番目の引数で始まりの日付の指定,4番目と5番目の引数で終わりの日付の範囲の指定を行っています。

“>=”&”2018-10-05”

という指定は,「2018年10月5日以降」という指定になります。1週間の始まりですね。

“>”&”2018-10-04”

としても同じです。続いて,終わりの日付は,同じように日付列を条件範囲とし,

“<“&”2018-10-12”

を条件にしています。つまり,「2018年10月12日より前」ですので,2018年10月11日の23時59分までのデータが条件に当てはまることになります。

“<=”&”2018-10-11”

でも同じです(むしろこっちのほうがわかりやすいかも)。これで,始まりの日付から終わりの日付までの間の合計語数が計算されます。ここを任意の幅に設定すれば,1週間ではなくとも3日でも4日でも10日でも同じようにできます。

月をまたいだ1週間の語数

同じ月内でのやり方は上の2つのやり方の組み合わせで対応できます。では,月をまたいでしまうときはどうすればよいでしょう。上述したように,月ごとにシートが異なるわけなので,別々のシートに記録された情報を統合する必要が出てきます。ただ,難しいことはなく単純に足し算すればよいだけです。

Screen Shot 2018-10-12 at 14.13.15

この例では,Friday2_201809というシートに9月分,Friday2_201810というシートに,importrange関数でデータを同期させています。9月分のデータで,9月の終わりの数日間(この例では9月28日~9月30日),10月のデータで10月1日からの数日間(この例では10月1日~10月4日)の語数を計算し,合算するという作業です。

つまり,それぞれの月でsumifs関数を使った式を作り,2つのsumifs関数式を+記号でつないであげれば,月をまたいだ場合の語数が計算できます。不等号のみと,不等号+等号の意味の違いは,上で説明したとおりです。

おわりに

ということで,前回の記事で紹介したGASでデータを引っ張る作業,importrange関数でデータを別ファイルにエクスポートする作業と,今回の記事で紹介したsumifs関数で日付指定する3つのパターンを使えば,一定期間の間の語数記録は簡単にできてしまいます。

エクセルが得意な方はすでにお気づきかもしれませんが,実は,1行目に指定する日付を終わりの日付にし,そのセルを日付の範囲指定に利用することもできます。つまり,上の画像で言えば,B1セルに”2018-09-27″,C1セルに”2018-10-04″,D1セルに”2018-10-11″のようにするということです。ただ,見たときに1週間の範囲がわかるほうがいいかなという理由で,そういうやり方はしていません。

ということで,下処理問題は解決されていませんが,だいたいの語数を記録して,学生がいつでも見れるようにするということについては,前回と今回の記事の内容でだいたいカバーできるのではないかなと思います。今の所第三段は予定していませんが,今後もしも「こういう事が必要だなぁ」という事が出てきたら更新するかもしれません。

なにをゆう たむらゆう。

おしまい。

注1. おそらくExcelのピボットテーブルなら,同じ列に当てはまる複数の条件でのフィルタリング可能だと思うので,Excelならピボットテーブルだけでいけると思います。

注2. 画像で一目瞭然ですが,毎週書き続けられている学生と,すでに脱落してしまっている学生が分かれてしまっているのは問題で,これについては何かしらの介入が必要だと思っています。

広告

Slackを用いた授業外ライティング活動の便利ワザ [Google Spreadsheet編①]

はじめに

便利ワザと言えるのかわかりませんが,とりあえず自分はこんな感じでやっていますというお話です。以下の話の発展です。

授業外でライティングする機会を確保するためのSlack

前提として,どのようにSlackを利用しているのかというお話です。google spreadsheetに書き出したデータの扱いについてにご興味がおありの方はこのセクションは飛ばしていただいてかまいません。私がslackを使おうと思ったのは以下のような考えからです。

授業外で,英語ライティングする機会を与えたい。できるだけ自由に書き込みができ,教員もそれを監視・管理しやすい。

やろうやろうと思っていたのですが,なかなかSlackの使い方もよくわかっていませんでした。共同研究の話等でもSlackを使い始めるようになり(サイボウズのサービス終了のため),自分もSlackに慣れてきたのでこの秋学期から導入することにしました。ここでも何回か記事を書いている外国語学部1回生向けのライティングの授業でのことです。

授業外でのライティングは,前記はLMSにある掲示板機能を使い,そこに私が毎週話題をポストしてそれについて各自意見を書き,お互いにコメントし合うというものでした。以下のリンクでそのことにも触れています。

https://tam07pb915.wordpress.com/2018/04/26/writing-class/

後期は,slackをclosed SNSのようにして使い,自由に英語でやりとりをするということを掲示板でのフォーラムライティングの代わりにすることにしました。前期のフォーラムライティングでは,「1人必ず○人にコメント」などとしたりして交流が生まれるようにしてみましたが,コメントが「テンプレ」化してしまったりしていたので,より自由にライティングのコミュニケーションができていったらいいなと思っています。

使い方のルールとしては以下のようにしています。

  • 一般的なルール
    • 自分のスマートフォンに必ずslackアプリをインストールすること
    • 書き込む情報については,既存のSNSに関するルールと同様,誹謗中傷や公序良俗に反する書き込み以外であれば,何について書き込んでも構わない(写真の投稿も可能)
    • 日本語での書き込みは一切禁止
    • 使い方等についての質問がある場合は,#generalに英語で書き込むこと
    • 他のクラスの人にも話題を共有したい場合には#randomに書き込むこと
    • 誰かの書き込みに返信(いわゆるリプ)をしたい場合は,”Start a thread”を使うこと
    • “Start a thread”機能を使うときは,”Also send to #channel-name”にチェックを入れること(こうすることで,メインのタイムラインでも返信が見れます)
    • 誰かに直接話しかけたいときは,@をつけること
    • 成績に算入するのは,自分のクラスのチャンネルに書き込んだもののみ(#generalや#randomや他のクラスのチャンネルに書き込んだものは語数に数えない)

評価方法は,毎週300語を学期の最終授業日まで続け,平均達成度(%)×5点を最終的な評定に加えるというものです。共通シラバスのため,授業外の課題に割ける評価の割合がもともと15点くらいしかないのでこのようにしています。そのために300語って結構えげつないなと思われる方もいるかもしれませんが,私もそう思っています。今学期の様子をとりあえず見てみて,ハードルが高すぎるために「コスパが悪い」と思われて取り組みが悪くなるということがあれば今後語数の基準を下げるかもしれません。

Slackデータのエクスポート

さて,上述のような形でslackを運用しているわけですが,私の扱い方だと「毎週300語」という設定にしているので,学生の立場からすれば「自分が今何語書き込んでいるのか」がいつでもわかるようになっていてほしいというのは当然でしょう。そのために教員がわざわざデータをチェックしてそれぞれの学生の書き込んだ語数を分析してまとめて報告するというのは無理でしょう。というかやってられません。そこで,エクスポート->語数の記録->まとめ,という一連の作業を自動化して,いつでも学生が見れるようにする必要があります。そこで,上に上げたスライドで浦野先生が紹介しているGoogle Apps Scriptを使うというわけです。浦野先生も紹介していますが,SlackのログをGoogle Spreadsheetに保存するという作業自体は以下の記事を参考にすればすぐにできます。

Slack のログを自動で Google Spreadsheet に保存する

問題は,その先で,語数の記録とそのまとめという部分になります。私自身がGoogle Apps Scriptをいじれないので,とりあえずは上記のリンクを参考にGoogle Spreadsheetにログを保存し,そのあとにGoogle Spreadsheetで頑張るということになります。Rでもログを取ってきてまとめて可視化みたいなことはできますが,自動化の部分が少し難ありです。Rのコードもそのうちここに投稿しようと思います。

Google SpreadsheetでSlackのログをいじる

Google Apps Script(GAS)は,自分で決めたトリガーで定期的に更新できるので,その頻度を好みに設定しておくだけで,定期的にデータを最新のものにするという作業はできます。最初は,ログのspreadsheetファイルにある各チャンネルのシートに列を追加して各行の書き込みの語数を記録し,それを別ファイルにピポットテーブルで吐き出すということを考えました。ちなみに,ログのデータは下の画像のような感じでチャンネルごとにシート1枚で記録されます。

A列が日付,B列が名前,C列が書き込まれた内容,D列はもとのJSON形式のデータです(注1)。最初は,このシートのE列に語数カウントの関数を入れていたわけです(excelで語数のカウント方法についてはググればすぐに見つかります)。ただ,それだとシートが更新されるとうまくいきませんでした。このチャンネルのシートをそのまま別シートで参照し,その参照したコピーのデータを使って…ということもやりましたがこれもうまくいかず。ということで,GASが作るスプレッドシートとは別のスプレッドシートファイルを作り,そこからもともとのスプレッドシートの中の特定のシートの情報を参照するという方法を取ることにしました。で,これexcelにもある関数のなのかは知らないんですが,Googleスプレッドシートには,importrangeという関数があり,これを使うことで別ファイルから参照ができます。以下のサイトなどが参考になりました。

【超便利】スプレッドシートで別シートから参照したり集計したりする方法まとめ

引数の基本は,importrange(“参照元ファイルURL”, “参照元シート名!特定セルor範囲)で,このimportrange関数は特定のセルのみを参照する場合と,範囲を指定する場合があるのですが,私は上の画像でいうA〜D列まですべてもってきたいので,範囲を指定することになります。とりあえず,1000行までを1シートに出すという設定だったような気がするので,A1:D1000を指定します。friday2というのは,金曜2限のチャンネルで,後ろのぼかしはslack上のチャンネルIDです。

このimportrange関数をA1セルに入力すると,シートにAからD列までのデータが取得され,参照元のシートがGASによって更新されれば,こちらのシートも更新されます。E列は手動で以下の画像のような関数を入れると,C列のセルの語数がE列のセルに記録されます。あとは,このE列の語数をB列の名前ごとに合計してくれれば,目的達成です。私は金曜の1限と2限に同じライティング科目の別クラスを持っているので,このログシートが2枚あり,そのまとめのシートも2枚あります。

まとめのシートで合計語数の表示

上画像のシート上でやってもいいんですが,見栄えもあるので別シートにまとめだけを作ります。これは,sumproduct関数をつかいます。sumproduct関数は,sumproduct(参照列=名前, 合計を数える列)のような形で使います。下記画像は,”Friday1_import”というシートに,importrange関数で参照元ファイルのデータをインポートしている場合の例です。A列に名前の一覧(注2),B2にこのsumproduct関数をいれます。あとはB2を下にコピーすれば,人数分の合計語数が表示されるというわけです。”Friday1_import”のシートには自動的にデータが追加されていくので,とりあえず1000行を超えないデータであればこれでなんとかなります。

棒グラフでも出したいということなら,範囲選択して棒グラフ挿入でOKです。あとは,このスプレッドシートのファイルを「閲覧可」の権限で共有してSlackに貼り付ければ,学生は自分の好きなタイミングで語数を確認できることになります。私の場合は「毎週300語」という課題なので,週ごとにデータを扱える必要があります。これはもう少し別の手続きが必要になるので,また次回ということに。今日はここまで。

なにをゆう たむらゆう。

おしまい。

注1: そもそも,ちゃんとした「下処理」をしないと,「@XXXXX has joined the channel」とかも記録されますし,絵文字1つが1語みたいになるので,ここでの語数は「アバウトな」語数ということになります。

注2: 私は試行錯誤しているときにピポットテーブルを使ったので名前の一覧はコピペでしたが,そうでない方はログデータの”@XXXX has joined the channel”の列だけソートして名前の一覧をゲットするなどの方法が必要です。

[R] sjPlotパッケージのバージョンアップ

Rで回帰モデルの図示をするときに私が使うパッケージは主に2つあって,1つはeffectsパッケージ,もう一つはsjPlotパッケージです。前者については,以前NagoyRで発表したことがあります。

effectsパッケージを用いた一般化線形モデルの可視化

後者は,lm, glm, lmer, glmerなどの関数で作った回帰モデルの結果が入ったオブジェクトを渡すと,その結果をggplot2に渡して可視化してくれます(その他にもいろんな可視化が可能ですが,私が使うのは主に回帰モデルの可視化です)。昔(数年前)までは,sjp.lm,sjp.glm,sjp.lmer,sjp.glmerなど,もとの回帰モデルに合わせて図示する際の関数を選ぶ仕様になっていました。そして,交互作用図を描きたいときは,sjp.int関数を使うというような。それが,最新版のsjPlotパッケージでは,これらの関数がなくなりすべてplot_modelという関数に統一されているようです(下記のサイトによると2017年10月にこの変更があったようです)。使用例は以下のサイトが参考になります。

„One function to rule them all“ – visualization of regression models in #rstats w/ #sjPlot

交互作用図は,type引数でintにするか,またはtypeをpredにして,termsで交互作用を指定するようです。

plot_model(fit, type=”int”)

or

plot_model(fit, type =”pred”, terms =c (“test”, “group”))

みたいな感じです。これについては,下記のページが参考になります。

 

Plotting Interaction Effects of Regression Models

より詳細な引数の説明などは以下のページに書いてあります(RDocumentationのページ)。

plot_model function | R Documentation

D院生のときに書いたスクリプトではGLMMの結果の可視化にsjp.glmerとかsjp.intを使っていたので,それらが動かなくなっていました。調べたらこういう仕様の変更があったと。一つの関数で,引数の組み合わせで色々な図が描けるというのは便利でいいですね。ただ,テーブル形式(HTML)で回帰モデルの結果を出力するsjt.lm系の関数は,lm, glmなどと組み合わせて,sjt.lm, sjt.glm, sjt.glmer, sjt.lmerなどのままのようです。

それから,最近lme4のモデル式の書き方でstanを使ったベイズ推定ができるbrmsというパッケージを知った(遅い)のですが,plot_model()はbrmsパッケージのモデルにも対応しているようです。まだ試してはいないので,いつかまたブログに書こうかなと思います。

では。

なにをゆう たむらゆう。

おしまい。

Visual World Paradigmの分析

だいぶ前に書きかけで放置していた記事の更新です。

7月1日,2日で言語科学会に行ってきました。心理言語系の発表が多く,講演やシンポジウムでの発表も含めて,Visual World Paradigm(以下,VWP)という手法を用いた研究が結構ありました。このVWPは,ざっくりいうと主に音声で言語刺激を与え,それを処理している最中の実験参与者の視線を計測するという手法です。このとき,参与者は主に視覚提示されたいくつかの絵や写真を見ています。そこで,どこをどれくらい見ているか,あるいはどのタイミングでどこに目線がいきやすくなるのかを観察することで,人間の言語処理の仕組みを明らかにしようとするわけです。

で,そこですごく気になったのが分析方法。普通の文処理中の視線計測実験では,文のどこ(どの単語,句)をどれくらい見ているかなどを分析しますが,VWPでは音声データが流れていく時間の経過とともに変化する視線の動きを追っているわけで,時系列データになります。文であれば,画面上に1行で提示できるほどの長さが限界ですので,多くとも10-15語程度,その中で,分析の対象となる区域は2-3程度だと思います(注1 )。

ところが,VWPの人たちってまずこの区切り方がめちゃっくちゃ恣意的だったりします。それでもって,25msとか50msごとにグラフに点を売ったりしているわけです(eg., Ito et al., 2017, BLC)。え?待って?てことは50msごとにモデル作ってそれぞれで「検定」っぽいことしているわけ?LME(GLMM)使ってるからどうのこうのとか言う問題じゃなく,その分析の仕方っておかしくないですか?要するにt検定を数十回繰り返しているのと同じわけで,そんなことしたら第1種の過誤が飛躍的に上昇してしまうはずですよね(注2)?

たまたまBLCで見つけたので他意はまったくないのですが,Ito et al. (2017)ではその部分に対して以下のような記述があります。

This way of analysing the time-course increases the likelihood of Type I errors, but we note that the differences reported below show consistently reliable effects over multiple bins.

確かに論文中の図をみると,条件ごとの傾向がかなりはっきり出ているわけですが,だからといってそんな50msごとに「有意!有意!」みたいなことやるのは正当化されないでしょう。だって,繰り返しが多いので有意水準をめちゃくちゃ保守的にしますなんてことはやってなくて,t値が2超えたら有意ってことでいくんでよろしくとかやってるのですから。

VWPは手法としてはすごく面白いのだけれど,分析の手法が全然追いついていないのではないかというような印象です。このパラダイムで分析をしていくためにはもともとの注視時間のデータを割合にする(つまりある単位で区切ったときに,その単位時間のなかでどの領域をどれほどの割合で注視しているかというデータにしている)ということがおそらく不可欠だからかもしれません。そうではないと,視線計測の時系列データから興味関心のあるデータをうまく取り出すことができないからです。もっといい方法ないのかなと思うのですが…

もし仮に単位時間を恣意的に設定して「観測点」のように捉えることは出発点としてOKだとしても,それはやっぱり時系列データ分析の手法を持ってくるほうがいいように思います。以下の記事のような考え方です。

二つの時系列データの間に「差」があるか判断するには

自分でこの時系列のデータ分析を実装する技術を持ち合わせているわけではないのですが(そもそも視線計測のデータセットを見たことがないのであまりイメージも浮かばない),誤差による上下動があるなかでの特定の条件での「変化点」を検知したり,あるいは区域ごとではなく「全体として」プロセスが異なることを分析するような考え方ができたらいいのではと思います。区域ごとに区切って分析するとはいえ,前の状態の影響を一切受けずに次の状態が決まるわけではなく,前の状態があるから次の状態に移行するわけで。たぶんこれって視線計測装置とか文処理にかぎらず,時系列になっている(観測点が時系列上に複数並ぶ)データに対してはすべて当てはまると思うんですよね。longitudinal studyとかも含めて。

私も結局はエンドユーザーなので,自分がそういうデータセットを前にしたときにうまく分析して解釈できる自信があるというわけではないのですが,なんかもやもやするなぁと思いながらずっと発表を聞いていたのでした。

なにをゆう たむらゆう。

おしまい。

 

注1. もっと解像度の低い視線計測装置だとヒートマップみたいなもので視覚的にどの辺を多く見ていたかとかやることもあるかと思います。パッセージ読んでるときの視線とか。

注2. これはもちろん自己ペース読み課題の分析にも当てはまるのでブーメランです。

[R] データフレームの中の英文の語数を数える

以前,エクセルで英文の中の特定の単語までの語数を数えるという記事を書きました。今回はそれよりもっと単純で,Rのデータフレームの中の1部に英文が入っていて,その英文の語数を数えてデータフレームに付け足したいなっていう作業です。

%e3%83%96%e3%83%ad%e3%82%b0%e8%a8%98%e4%ba%8b%e7%94%a8_20170218

こんな感じでロング型のデータがdatという変数にあるとします。このデータフレームのなかの,sentenceという列には英文が入っていますが,それぞれ長さが違います。文が長いほうが反応時間も長くなっちゃいますから,そのために英文に含まれる語数を数えてそれも分析に使いましょうということです。僕がやっているような反応時間を扱う実験ではよくありがちな手続きです。本来なら刺激文作るときにそれもちゃんと数えておいて(エクセルで語数数えるのは簡単です。前掲の記事の一部がそれです),結果が出力される際にちゃんと語数の情報も含めておけばいいんですけど,まぁ今回はそれ忘れちゃったんでRの中でやってしまおうという事後的な対処法です。

さてさて前置きが長くなりました。Rでの文字列処理の基本は「分ける」・「数える」だと思います。僕はそんなにRで文字列処理しないんですが,今回の場合で言うと,データフレームの要素に含まれる英文をスペースで区切り,区切ったときにいくつかに分けられた要素の数を数えることで語数をカウントするという手続きになります。

今回使う関数は次の4つです。

  • (as.character関数)
  • strsplit関数
  • length関数
  • sapply関数

順番に説明していきます。strsplit関数は

strsplit(分けたいもの,区切り文字)

という感じで使います。今回の場合,分けたいのはdatの中のsentenceという列(dat$sentence)で,スペースで区切りますから,

strsplit(dat$sentence, ” “)

とすれば,

%e3%83%96%e3%83%ad%e3%82%b0%e8%a8%98%e4%ba%8b%e7%94%a8_20170218_2

こんな感じで単語ごとに区切ってくれます。この出力は1文ずつのリスト形式になっていて,そのリストの要素が英文を構成する単語になっています。もしも,このstrsplit関数がうまくいかなければ,

is.character(dat$sentence)

として,データフレーム中の英文が文字列形式で格納されているかどうか確かめてみてください。因子型(factorial)になっていると,strsplit関数は適用されません。これを変えるのが,as.character関数です。

strsplit(as.character(dat$sentence))

または,

dat$sentence<-as.character(dat$sentence)
strsplit(dat$sentence, ” “)

としてみてください。

さて,これでひとまず「区切る」作業はできましたが,まだ「数える」にはいたっていませんよね。数えるためによく使われるのが要素の数を数えるlength関数です。ただし,例えば

length(strsplit(as.character(dat$sentence)))

としてもうまくいきません。これでは出力されたリストの要素を数えるので,元のデータフレームの行数が表示されるはずです。ここでは,「リストのそれぞれの要素に対してlength関数を適用したい」わけなので,もうひと工夫必要です。そこで使うのがapply系列のsapply関数です。この関数は,

sapply(リスト,関数)

という形で引数を与えてあげると,リストの各要素に関数を適用してくれます。これと同じことができるのがlapply関数です。lapply関数を使うと,結果の出力もリスト形式で返されます。ただ,ここでは語数が並んだベクトル形式のものを手に入れて,それをもとのデータフレーム(dat)に追加したいわけなので,sapply関数のほうがベターです。lapply関数にすると,そのあとリストをベクトルに直すunlist関数を使う必要があって二度手間になってしまうので。というわけで,

sapply(strsplit(as.character(dat$sentence), ” “), length)

こうしてあげれば「スペースで区切る-> 語数を数える -> ベクトル形式で結果をゲット」という作業が完了します。あとは,

dat$num.words<-sapply(strsplit(as.character(dat$sentence), ” “), length)

としてあげれば,datに新しくnum.wordsという列が追加され,そこにはsentenceの列に入っている英文の語数が格納されることになります。めでたしめでたし。といきたいところなのですが,最後におまけでもうひとつ。

上の書き方だと,いくつもの関数が入れ子状態になっていて,一見して作業がわかりにくいですよね?そこでパイプ演算子を使って同じことをもう少しわかりやすく書き直してあげます。

dat$sentence%>% #datの中のsentenceという列を取ってくる
as.character%>% #文字列型に変換
strsplit(. , ” “)%>% #スペースで区切る
sapply(., length) -> dat$num.words #リストの要素ごとに語数を数えてdatというデータフレームのnum.wordsという列にいれる

こうやって書くと,作業の過程が見えてわかりやすいですよね。ちなみにこのパイプ演算子はパッケージ依存です。dplyrやtidyrを使うときに使うやつですね。たぶんこれらのパッケージを読み込んでいればパイプ演算子も使えるようになっているはずです。もともとはmagrittrというパッケージに入っているのがパイプ演算子が%>%なんですよね(たしか)?

というわけでlapplyの出力をunlistしてできたできたと思っていたらsapply関数の存在に気づいてなんだよ二度手間じゃんかよって思ったのでブログ記事にしました。

なにをゆう たむらゆう。

おしまい。

 

 

 

[R] 欠損値を1つでも含む行を抽出

ものすごい初歩的なことだったのに,調べてもなかなかヒットせずに格闘したのでメモ。

RでNAを処理する方法はたくさんあって,ある特定の列にNAが含まれる行だけ引っ張ってくるなどは結構簡単だったりする

subset(dat, is.na(dat$A))

とかでいい。ただし,データフレームにある複数の変数の中で1つでもNAが入っているかどうかとなるとちょっとどうしたらいいんだこれは,となる。

na.omit(dat)としてNAが含まれている行を全削除すればいいのでは?となるかもしれないが,実は今扱っているデータがlong型のうえにかなり複雑なデータフレームになっていて,同じidが複数行にまたがってしまっているという状態。実験のデザインがかなり複雑なのでこれはこれで仕方がない。

つまり,複数行のうち1行でもNAがあるidは,NAが含まれない行でも削除しないといけない。そのためには,NAが含まれる行のidを特定する必要があるわけだ。そこで,欠損値を含む行を抽出し,その中からid列だけを取ってくるという手続きを取る必要がでてきた。こうすれば,あとはdplyrのfilter関数なり,subset関数なりでそのidが含まれる行をすべて抜いて新しいデータセットを作ればよい。

それで,この,NA行の特定をベクトル形式で返してくれる関数が,complete.casesという関数。is.na関数にデータフレームを渡すと,その結果は行列(matrix)形式になる。これはこれで,どの行のどの列にNAがあるかを確かめるのにいいのだけれど,こちらが欲しいのは,「何行目」にNAが含まれるのかという情報だけ。そこで,complete.cases関数を以下のような感じで使う。

subset(dat, complete.cases(dat)==F)

あるいは,

dat%>%
filter(complete.cases(dat)==F)%>%
select(id)

上の方法だと,結果はデータフレームだけど,下の方法だとid列を引っ張ってくるところまでやるので,これをなにか別の変数(ここではmissIDにしてみる)にいれてあげて,

subset(dat,id != missID)

とかやれば,id列でmissIDに該当しない行だけを抽出してくれる。下書き状態で放置されていたからとりあえず最後まで書いたけど,自分がなんのデータに対してこの手続をしていたのかはもう思い出せないw

データ分析業務ばかりやってるのは楽しいからいいんですけどね。

なにをゆう たむらゆう。

おしまい。

※追記(2017.03.11)

subset(dat,id != missID)

とやってもうまくいかないことがあるようです,というか多分うまくいきません(自分でやってみてダメだったので)。多分ですが原因の1つはmissIDがデータフレーム形式になっている場合。ベクトルになおしてください。もう1つの原因は,subsetなりdplyrのfilterなりで複数の条件を設定する場合に,参照先がベクトル形式の場合には通常の論理演算記号が機能しないことです。多分こちらのほうが大問題。時間が経ってから書いたからかこのことをすっかり忘れていましたすみません。正しくは%in%を使って,

subset(dat, !id %in% missID)

とします。この場合,データフレームの中の参照先の列名の前に!をつけます。これをつけないと「当てはまるもの」を取り出すことになります。

dplyrを使ってみる:long型から記述統計

今までデータをあれこれいじったり記述統計出したりするのはExcelで色々やってからRにいれて分析という流れでした。dplyr使ったら便利そう…というのはなんとなくわかっていたんですが,自分がよく扱うようなデータセットの構造に対してどうやって使えば今までやってたことが楽になるのかいまいちイメージがわかずにずっと放置していました。最近色々あってようやく基礎中の基礎だけを覚えたので以下メモ書きです。

// <![CDATA[
window.onload = function() {
var imgs = document.getElementsByTagName('img'), i, img;
for (i = 0; i

 

まずはサンプルのデータを作ります

#事前・事後・遅延事後でCAFのデータを取ってるというデザイン
pre.fluency <-rnorm(50,mean=10,sd=2)
pre.accuracy<-rnorm(50,mean=7,sd=2)
pre.complexity<-rnorm(50,mean=4,sd=2)
post.fluency<-rnorm(50,mean=14,sd=4)
post.accuracy<-rnorm(50,mean=10,sd=3)
post.complexity<-rnorm(50,mean=7,sd=2)
delayed.fluency<-rnorm(50,mean=12,sd=2)
delayed.accuracy<-rnorm(50,mean=8,sd=1)
delayed.complexity<-rnorm(50,mean=6,sd=1)

#それぞれの列を横にくっつける
dat<-cbind(pre.fluency,pre.accuracy,pre.complexity,post.fluency,post.accuracy,post.complexity,delayed.fluency,delayed.accuracy,delayed.complexity)
dat<-as.data.frame(dat) #データフレーム型に変換
dat$subject<-rep(1:50) #実験参与者のID列をつける

とりあえずこんな感じでいまデータを持ってるとする

head(dat)
##   pre.fluency pre.accuracy pre.complexity post.fluency post.accuracy
## 1   11.916630     7.480500       4.644798     8.431656     13.070707
## 2   10.719751     7.090714       6.566487    13.652658     11.462576
## 3   11.980852     6.182161       5.731289    16.580042     11.287683
## 4   11.109400     7.256950       5.897070    16.966183      9.497555
## 5   10.488391     5.449349       2.673285    15.778588      9.994575
## 6    9.076144     8.621538       2.122296     9.465845      7.577611
##   post.complexity delayed.fluency delayed.accuracy delayed.complexity
## 1        8.639772       12.961946         7.740613           7.042510
## 2        6.397390        9.921947         7.520258           6.951231
## 3        6.917590       10.478401         9.378301           4.972710
## 4        5.897965        6.738074         8.983224           5.045168
## 5        6.214526       10.694636         8.510670           5.184044
## 6        7.644466        8.666421         8.863990           4.719461
##   subject
## 1       1
## 2       2
## 3       3
## 4       4
## 5       5
## 6       6

まずは縦横変換

参考までに以前tidyrの紹介を書いたブログ→変数が多い研究デザイン系の縦横変換(tidyr)

library(tidyr)
dat%>%
  gather(key=variable,value=score,-subject)%>%
    separate(col=variable,into=c('test','measure'))->dat2
head(dat2) #test, measure, scoreという列ができてるか確認
##   subject test measure     score
## 1       1  pre fluency 11.916630
## 2       2  pre fluency 10.719751
## 3       3  pre fluency 11.980852
## 4       4  pre fluency 11.109400
## 5       5  pre fluency 10.488391
## 6       6  pre fluency  9.076144

悩み

でもさーこうやってlong型にしちゃうとさー例えば事前のときの流暢さの指標の平均値とか出せないじゃん?wide型になってたら全部横にならんでるから列ごとに平均求めればいいだけじゃん?

long型になってても層ごとの記述統計出せちゃう

そう,dplyrならね

library(dplyr)
dat2%>%
    group_by(test)%>%
    group_by(measure,add=T)%>%
  summarise_each(funs(mean,sd,min,max),-subject) #summarise(mean=mean(score),sd=sd(score),min=min(score),max=max(score))でも同じ。もともとsummarise_eachは複数列に同じ関数を適用するときに威力を発揮するものだけど
## Source: local data frame [9 x 6]
## Groups: test [?]
## 
##      test    measure      mean        sd        min       max
##     (chr)      (chr)     (dbl)     (dbl)      (dbl)     (dbl)
## 1 delayed   accuracy  8.050833 1.0231177  5.5291358 10.250133
## 2 delayed complexity  6.043974 0.8633653  4.1740878  7.908483
## 3 delayed    fluency 11.966818 2.2327496  6.7231359 15.657528
## 4    post   accuracy  9.529745 2.4594148  2.7470626 16.876272
## 5    post complexity  7.211917 1.9710329  2.3591702 11.759346
## 6    post    fluency 14.624789 3.8821630  7.3521391 21.161635
## 7     pre   accuracy  6.941050 1.7114477  3.3338811 10.798454
## 8     pre complexity  4.326063 2.1271468 -0.7051241 10.391882
## 9     pre    fluency 10.239550 2.0043330  5.8044643 14.556087

※このデータの場合,test,measure,score,subjectの4つしかなく,グルーピング変数で2つ使っていて,-subjectするので必然的にscoreだけの計算になるけど,もっと列数多くて記述統計出したいのは1つだけの時は列を指定してあげる(下の例参照)


追記

  • もしも人ごとに1試行が1行になっているようなlong型の場合は,最初に人ごとにgroup_byすればOK
#こんな感じ
#dat2%>%
#  group_by(subject)%>%
#  group_by(test)%>%
#  group_by(measure,add=T)%>%
#  summarise_each(funs(mean,sd,min,max),score)
  • もし値に欠損があるときに関数が複数あるとちょっとめんどうだけど…
#こんな感じ
#dat2%>%
#  group_by(subject)%>%
#  group_by(test)%>%
#  group_by(measure,add=T)%>%
#  summarise_each(funs(mean(.,na.rm=T),sd(.,na.rm=T),min(.,na.rm=T),max(.,na.rm=T)),score)

 

ハッ,なに言ってるのこの人そんなのdplyrの威力をぜ~んぜんっ活かせてないんだからね!っていうのもあるかと思うのでもっと勉強したいと思いますがとりあえずtidyrからのdplyrを外国語教育研究のありそうなデータセットの形でやってみるとこんな感じになりますよというお話でした。

参考サイト

dplyrを使いこなす!基礎編

R dplyr, tidyr でのグルーピング/集約/変換処理まとめ

 

なにをゆう たむらゆう。

おしまい。

20160516追記

よくよく考えてみると,そもそもCAFって指標ごとに分析するからaccuracy, fluency, complexityの3列で待ってる形じゃないと分析できなくない?

そんなときはgather->separateのあとにspreadしてあげる

dat2%>%
  spread(measure,score) ->dat3 #measureごとにscoreの列を作る
head(dat3)
##   subject    test  accuracy complexity   fluency
## 1       1 delayed  7.740613   7.042510 12.961946
## 2       1    post 13.070707   8.639772  8.431656
## 3       1     pre  7.480500   4.644798 11.916630
## 4       2 delayed  7.520258   6.951231  9.921947
## 5       2    post 11.462576   6.397390 13.652658
## 6       2     pre  7.090714   6.566487 10.719751

あとは上のやり方と同じ

dat3%>%
  group_by(test)%>%
  summarise_each(funs(mean,sd,min,max),-subject)
## Source: local data frame [3 x 13]
## 
##      test accuracy_mean complexity_mean fluency_mean accuracy_sd
##     (chr)         (dbl)           (dbl)        (dbl)       (dbl)
## 1 delayed      8.050833        6.043974     11.96682    1.023118
## 2    post      9.529745        7.211917     14.62479    2.459415
## 3     pre      6.941050        4.326063     10.23955    1.711448
## Variables not shown: complexity_sd (dbl), fluency_sd (dbl), accuracy_min
##   (dbl), complexity_min (dbl), fluency_min (dbl), accuracy_max (dbl),
##   complexity_max (dbl), fluency_max (dbl)

列数が多いので表示されていないけど,出力はデータフレームなので,出力結果を何かの変数にいれてあげれば良い


ちなみに,実をいうとwide型の段階で列ごとにapplyしたほうが(ry

library(psych)
apply(dat,2,describe)
## $pre.fluency
##   vars  n  mean sd median trimmed  mad min   max range skew kurtosis   se
## 1    1 50 10.24  2  10.45   10.24 2.21 5.8 14.56  8.75    0     -0.6 0.28
## 
## $pre.accuracy
##   vars  n mean   sd median trimmed  mad  min  max range skew kurtosis   se
## 1    1 50 6.94 1.71   7.15    6.93 1.79 3.33 10.8  7.46 0.06    -0.43 0.24
## 
## $pre.complexity
##   vars  n mean   sd median trimmed  mad   min   max range skew kurtosis
## 1    1 50 4.33 2.13   4.09    4.33 2.07 -0.71 10.39  11.1 0.09     0.33
##    se
## 1 0.3
## 
## $post.fluency
##   vars  n  mean   sd median trimmed  mad  min   max range  skew kurtosis
## 1    1 50 14.62 3.88  14.78   14.67 5.19 7.35 21.16 13.81 -0.09    -1.27
##     se
## 1 0.55
## 
## $post.accuracy
##   vars  n mean   sd median trimmed  mad  min   max range skew kurtosis
## 1    1 50 9.53 2.46   9.48    9.44 2.69 2.75 16.88 14.13 0.24     0.67
##     se
## 1 0.35
## 
## $post.complexity
##   vars  n mean   sd median trimmed  mad  min   max range  skew kurtosis
## 1    1 50 7.21 1.97   6.93    7.22 2.18 2.36 11.76   9.4 -0.07    -0.31
##     se
## 1 0.28
## 
## $delayed.fluency
##   vars  n  mean   sd median trimmed  mad  min   max range  skew kurtosis
## 1    1 50 11.97 2.23  12.42   12.09 2.51 6.72 15.66  8.93 -0.42    -0.52
##     se
## 1 0.32
## 
## $delayed.accuracy
##   vars  n mean   sd median trimmed  mad  min   max range  skew kurtosis
## 1    1 50 8.05 1.02      8    8.06 1.06 5.53 10.25  4.72 -0.15     -0.2
##     se
## 1 0.14
## 
## $delayed.complexity
##   vars  n mean   sd median trimmed  mad  min  max range skew kurtosis   se
## 1    1 50 6.04 0.86   5.97    6.05 1.01 4.17 7.91  3.73 0.02    -0.65 0.12
## 
## $subject
##   vars  n mean    sd median trimmed   mad min max range skew kurtosis   se
## 1    1 50 25.5 14.58   25.5    25.5 18.53   1  50    49    0    -1.27 2.06