Tools for experiments-生成拉丁方(counterbalanced orders)

問題

你想要生成平衡序列用于實驗然低。

方案

函數(shù) latinsquare() (在下方定義) 可以被用來生成拉丁方漫蛔。

latinsquare(4)
#>      [,1] [,2] [,3] [,4]
#> [1,]    1    2    4    3
#> [2,]    2    1    3    4
#> [3,]    3    4    1    2
#> [4,]    4    3    2    1


# To generate 2 Latin squares of size 4 (in sequence)
latinsquare(4, reps=2)
#>      [,1] [,2] [,3] [,4]
#> [1,]    3    4    1    2
#> [2,]    4    3    2    1
#> [3,]    1    2    4    3
#> [4,]    2    1    3    4
#> [5,]    4    2    1    3
#> [6,]    2    3    4    1
#> [7,]    1    4    3    2
#> [8,]    3    1    2    4


# It is better to put the random seed in the function call, to make it repeatable
# This will return the same sequence of two Latin squares every time
latinsquare(4, reps=2, seed=5873)
#>      [,1] [,2] [,3] [,4]
#> [1,]    1    4    2    3
#> [2,]    4    1    3    2
#> [3,]    2    3    4    1
#> [4,]    3    2    1    4
#> [5,]    3    2    4    1
#> [6,]    1    4    2    3
#> [7,]    4    3    1    2
#> [8,]    2    1    3    4

存在大小為4的576個拉丁方豪嗽。函數(shù)latinsquare會隨機選擇其中n個并以序列形式返回它們劝赔。這被稱為重復(fù)拉丁方設(shè)計 丁鹉。

一旦你生成了自己的拉丁方,你需要確保不存在許多重復(fù)的序列召锈。這在小型拉丁方中非常普遍 (3x3 or 4x4)旁振。

生成拉丁方的函數(shù)

這個函數(shù)用來生成拉丁方。它使用了略微暴力的算法來生成每個拉丁方涨岁,有時候在給定位置的數(shù)字用完了它可能會失敗规求。這種情況下,它會嘗試再試一試卵惦。可能存在一種更好的辦法吧瓦戚,但我并不清楚沮尿。

## - len is the size of the latin square
## - reps is the number of repetitions - how many Latin squares to generate
## - seed is a random seed that can be used to generate repeatable sequences
## - returnstrings tells it to return a vector of char strings for each square,
##    instead of a big matrix. This option is only really used for checking the
##    randomness of the squares.
latinsquare <- function(len, reps=1, seed=NA, returnstrings=FALSE) {

    # Save the old random seed and use the new one, if present
    if (!is.na(seed)) {
        if (exists(".Random.seed"))  { saved.seed <- .Random.seed }
        else                         { saved.seed <- NA }
        set.seed(seed)
    }
    
    # This matrix will contain all the individual squares
    allsq <- matrix(nrow=reps*len, ncol=len)
    
    # Store a string id of each square if requested
    if (returnstrings) {  squareid <- vector(mode = "character", length = reps) }

    # Get a random element from a vector (the built-in sample function annoyingly
    #   has different behavior if there's only one element in x)
    sample1 <- function(x) {
        if (length(x)==1) { return(x) }
        else              { return(sample(x,1)) }
    }
    
    # Generate each of n individual squares
    for (n in 1:reps) {

        # Generate an empty square
        sq <- matrix(nrow=len, ncol=len) 

        # If we fill the square sequentially from top left, some latin squares
        # are more probable than others.  So we have to do it random order,
        # all over the square.
        # The rough procedure is:
        # - randomly select a cell that is currently NA (call it the target cell)
        # - find all the NA cells sharing the same row or column as the target
        # - fill the target cell
        # - fill the other cells sharing the row/col
        # - If it ever is impossible to fill a cell because all the numbers
        #    are already used, then quit and start over with a new square.
        # In short, it picks a random empty cell, fills it, then fills in the 
        # other empty cells in the "cross" in random order. If we went totally randomly
        # (without the cross), the failure rate is much higher.
        while (any(is.na(sq))) {

            # Pick a random cell which is currently NA
            k <- sample1(which(is.na(sq)))
            
            i <- (k-1) %% len +1       # Get the row num
            j <- floor((k-1) / len) +1 # Get the col num
            
            # Find the other NA cells in the "cross" centered at i,j
            sqrow <- sq[i,]
            sqcol <- sq[,j]

            # A matrix of coordinates of all the NA cells in the cross
            openCell <-rbind( cbind(which(is.na(sqcol)), j),
                              cbind(i, which(is.na(sqrow))))
            # Randomize fill order
            openCell <- openCell[sample(nrow(openCell)),]
            
            # Put center cell at top of list, so that it gets filled first
            openCell <- rbind(c(i,j), openCell)
            # There will now be three entries for the center cell, so remove duplicated entries
            # Need to make sure it's a matrix -- otherwise, if there's just 
            # one row, it turns into a vector, which causes problems
            openCell <- matrix(openCell[!duplicated(openCell),], ncol=2)

            # Fill in the center of the cross, then the other open spaces in the cross
            for (c in 1:nrow(openCell)) {
                # The current cell to fill
                ci <- openCell[c,1]
                cj <- openCell[c,2]
                # Get the numbers that are unused in the "cross" centered on i,j
                freeNum <- which(!(1:len %in% c(sq[ci,], sq[,cj])))

                # Fill in this location on the square
                if (length(freeNum)>0) { sq[ci,cj] <- sample1(freeNum) }
                else  {
                    # Failed attempt - no available numbers
                    # Re-generate empty square
                    sq <- matrix(nrow=len, ncol=len)

                    # Break out of loop
                    break;
                }
            }
        }
        
        # Store the individual square into the matrix containing all squares
        allsqrows <- ((n-1)*len) + 1:len
        allsq[allsqrows,] <- sq
        
        # Store a string representation of the square if requested. Each unique
        # square has a unique string.
        if (returnstrings) { squareid[n] <- paste(sq, collapse="") }

    }

    # Restore the old random seed, if present
    if (!is.na(seed) && !is.na(saved.seed)) { .Random.seed <- saved.seed }

    if (returnstrings) { return(squareid) }
    else               { return(allsq) }
}

檢查函數(shù)的隨機性

一些生成拉丁方的算法并不是非常的隨機。4x4的拉丁方有576種较解,它們每一種都應(yīng)該有相等的概率被生成畜疾,但一些算法沒有做到∮∠危可能我們沒有必要檢查上面的函數(shù)啡捶,但這里確實有辦法可以做到這一點。前面我使用的算法并沒有好的隨機分布奸焙,我們運行下面的代碼可以發(fā)現(xiàn)這一點瞎暑。

這個代碼創(chuàng)建10,000個4x4的拉丁方,然后計算這576個唯一拉丁方出現(xiàn)的頻數(shù)与帆。計數(shù)結(jié)果應(yīng)該形成一個不是特別寬的正態(tài)分布了赌;否則這個分布就不是很隨機了。我相信期望的標(biāo)準(zhǔn)差是根號(10000/576)(假設(shè)隨機生成拉丁方)玄糟。

# Set up the size and number of squares to generate
squaresize    <- 4
numsquares    <- 10000

# Get number of unique squares of a given size.
# There is not a general solution to finding the number of unique nxn squares
# so we just hard-code the values here. (From http://oeis.org/A002860)
uniquesquares <- c(1, 2, 12, 576, 161280, 812851200)[squaresize]

# Generate the squares
s <- latinsquare(squaresize, numsquares, seed=122, returnstrings=TRUE)

# Get the list of all squares and counts for each
slist   <- rle(sort(s))
scounts <- slist[[1]]

hist(scounts, breaks=(min(scounts):(max(scounts)+1)-.5))
cat(sprintf("Expected and actual standard deviation: %.4f, %.4f\n",
              sqrt(numsquares/uniquesquares), sd(scounts) ))
#> Expected and actual standard deviation: 4.1667, 4.0883

原文鏈接:http://www.cookbook-r.com/Tools_for_experiments/Generating_counterbalanced_orders/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末勿她,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子阵翎,更是在濱河造成了極大的恐慌逢并,老刑警劉巖之剧,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異砍聊,居然都是意外死亡背稼,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門辩恼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來雇庙,“玉大人,你說我怎么就攤上這事灶伊〗埃” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵聘萨,是天一觀的道長竹椒。 經(jīng)常有香客問我,道長米辐,這世上最難降的妖魔是什么胸完? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮翘贮,結(jié)果婚禮上赊窥,老公的妹妹穿的比我還像新娘。我一直安慰自己狸页,他們只是感情好锨能,可當(dāng)我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著芍耘,像睡著了一般址遇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上斋竞,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天倔约,我揣著相機與錄音,去河邊找鬼坝初。 笑死浸剩,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鳄袍。 我是一名探鬼主播乒省,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼畦木!你這毒婦竟也來了袖扛?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蛆封,沒想到半個月后唇礁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡惨篱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年盏筐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砸讳。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡琢融,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出簿寂,到底是詐尸還是另有隱情漾抬,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布常遂,位于F島的核電站纳令,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏克胳。R本人自食惡果不足惜平绩,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望漠另。 院中可真熱鬧捏雌,春花似錦、人聲如沸笆搓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽砚作。三九已至,卻和暖如春嘹锁,著一層夾襖步出監(jiān)牢的瞬間葫录,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工领猾, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留米同,地道東北人。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓摔竿,卻偏偏與公主長得像面粮,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子继低,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容