(続)Rで学生・生徒を指定した人数のグループに分ける関数

先日のNagoya.R #18で、以下の記事のグループ分け関数についてお話させてもらった。

Rで学生・生徒を指定した人数のグループに分ける関数

この関数について吉野睦さんから、もっと簡単に同じことができることを教えていただきました。この場をお借りして感謝申し上げます。ありがとうございました。ご提供いただいたコードを許可をいただいた上で以下に転載させていただきます。

rm(list=ls())

# エクセル名簿から貼り付けるとこんな感じ(改行だけ)

x <- c("
川口勇作
川口勇作
川口勇作
川口勇作
川口勇作
川口勇作
")
# こんな感じに縦に並んでいるものを貼り付ける

# 改行コードで分割する
x <- strsplit(x,"\n")[[1]]           # なぜかリスト形式になってしまうので[[1]]
x <- x[!nchar(x)==0]                 # 空文字列を取り除く

# ここからがメインルーチン
class <- 10
index <- rep(1:class,len=length(x))  # クラス番号生成
index <- sample(index)               # クラス番号をシャッフルする
x <- split(x,index)                  # クラス分けする
print(x,quote=FALSE)                 # 名前のダブルコーテーションを取って表示

流れとしては以下のとおり。

エクセルから、ダブルコーテーションなし、カンマなし、改行のみで貼り付けたことを想定し、

strsplit()関数で名前データ生成

あとはメインルーチンで、

クラス数を指定
rep()関数でクラス番号を生成
sample()関数でシャッフル
split()関数でクラス分け
print()関数で、ダブルコーテーションなし表示

自分はfor文の中でfor文を回すという効率の悪いやり方をしていた。以前Python Bootcampに参加したときにも感じたことだが、今の自分にはこういう効率の良いコードを書く能力が欠けているようだ。フィードバックをいただける環境に感謝。


【追記 2018/9/2】吉野さんから以下のような追加情報をいただきました。ありがとうございました。

最後の、

print(x,quote=FALSE)

を、次のように変更して頂くと、リスト形式がデータフレームに変更できます。

y <- lapply(x,function(z){c(z,rep("",max(sapply(x,length))-length(z)))})
xx <- do.call(cbind,y)
print(xx,quote=FALSE)

参加予定ですか ?(必須)



Rで学生・生徒を指定した人数のグループに分ける関数

【追記 2018/5/13】コードをWordpressに貼り付けたときに一部の文字(””)が違う文字として貼り付けられてしまっていることに気づきました。現状では、そのまま貼り付けただけではエラーになりますので、ご注意ください。 -> 解決しました。

【追記 2018/5/29】特定の人数でエラーを吐くバグを修正しました。バグをご指摘くださった田村さんありがとうございました。

【追記 2018/5/31】出力の”member”が”menber”になってました。どれだけ麺好きなんだ。ということで修正しました。

【追記 2018/8/24】この関数について、Nagoya.Rでもっと簡単に同じことができるというコメントをいただきました。詳しくはこちら


表題の件について。前々から作ろうと思っていたけど、近いうちにNagoya.Rをやることになりそうなので、話の種に作ってみた。

以下をRのコンソールに貼り付ける。

Windowsの方はこちら

groupfunc <- function(p){
	d <- as.vector(read.table("clipboard", header = F, sep = ",")[,1])
 
	#学生数
	n <- length(d)
 
	#余りの人数
	r <- n%%p
 
	#空の行列を作成
	if(1 <= r){
		tb <- matrix(, nrow = trunc(n/p), ncol = (p+1)) #余りが1以上であれば、余分に1列を追加
	}else{
		tb <- matrix(, nrow = trunc(n/p), ncol = p)
	}
 
	#余った学生がいなくなるまで1人多くサンプリング
	for(i in 1:trunc(n/p)){
		if(i <= r){
			for(j in 1:(p+1)){
				tb[i,j] <- sample(d, 1)
				d <- d[-which(d %in% tb[i,j])]
			}
		}else{
			for(j in 1:p){
				tb[i,j] <- sample(d, 1)
				d <- d[-which(d %in% tb[i,j])]
			}
		}
	}
	result <- as.data.frame(tb) 
 
	#空行の削除(参考:http://id.fnshr.info/2017/08/14/r-blank-row-col/)
	#	is_blank の定義
	is_blank <- function(x) {is.na(x) | x == ""}
 
	# すべてが空欄である行を探す
	unnecessary_row <- apply(result, 1,
													 function(x){
														 all(is_blank(x))
													 })
 
	# 「すべてが空欄である行」以外を残す(=空行の除去)
	result <- result[!unnecessary_row,]
 
	#見出し行に名前を付与
	namae <- "member.1"
	for(k in 2:ncol(result)){
		namae <- append(namae, paste("member.", k))
	}
	colnames(result) <- namae
	result
}

【追記 2018/5/13】Macの方はこちらを。Macでは動かないことをご指摘くださった田村さんありがとうございました!

groupfunc <- function(p){
	d <- as.vector(read.table(pipe("pbpaste"), header = F, sep = ",")[,1])
 
	#学生数
	n <- length(d)
 
	#余りの人数
	r <- n%%p
 
	#空の行列を作成
	if(1 <= r){
		tb <- matrix(, nrow = trunc(n/p), ncol = (p+1)) #余りが1以上であれば、余分に1列を追加
	}else{
		tb <- matrix(, nrow = trunc(n/p), ncol = p)
	}
 
	#余った学生がいなくなるまで1人多くサンプリング
	for(i in 1:trunc(n/p)){
		if(i <= r){
			for(j in 1:(p+1)){
				tb[i,j] <- sample(d, 1)
				d <- d[-which(d %in% tb[i,j])]
			}
		}else{
			for(j in 1:p){
				tb[i,j] <- sample(d, 1)
				d <- d[-which(d %in% tb[i,j])]
			}
		}
	}
	result <- as.data.frame(tb) 
 
	#空行の削除(参考:http://id.fnshr.info/2017/08/14/r-blank-row-col/)
	#	is_blank の定義
	is_blank <- function(x) {is.na(x) | x == ""}
 
	# すべてが空欄である行を探す
	unnecessary_row <- apply(result, 1,
													 function(x){
														 all(is_blank(x))
													 })
 
	# 「すべてが空欄である行」以外を残す(=空行の除去)
	result <- result[!unnecessary_row,]
 
	#見出し行に名前を付与
	namae <- "member.1"
	for(k in 2:ncol(result)){
		namae <- append(namae, paste("member.", k))
	}
	colnames(result) <- namae
	result
}

( )の中の引数でグループ毎の人数を指定する。2人グループ(要するにペア)なら2を、3人グループなら3を入れる、といった感じで。

空行の削除は、以下の記事を参考とさせていただきました。

Rで空行・空列を除去する方法

人数がグループ数で割り切れない場合、余った人を各グループに1人ずつ割り当てるような仕様となっている。例えば30人で4人グループを作る場合、4人グループが5組と、5人グループが2組できる。これは自分が授業内で即興でグループを作るときのアルゴリズムに則っている。「いや自分なら4人グループ7組と2人ペア1組にする」という先生がいましたら、この関数はそういうのには対応していませんごめんなさい。

この仕様のせいで、思ったようなグループの人数にならないときがある。例えば40人で7人グループを作るとき、余った5人をできたグループに割り当てていくと、すべてのグループが8人になってしまう。これは余りの人数などで例外処理をしていけばいいのだろうけど、しんどいので今回は割愛。そもそも7人グループとか作ることなんて滅多にないと思う。

これをShinyアプリにするのが本当のねらいなのだけど、それはまた時間のあるときに。