*! 1.1.1 14dec2020 *! Wouter Wakker wouter.wakker@outlook.com * 1.1.1 14dec2020 if in syntax documented * 1.1.0 10dec2020 byable * 1.0.2 08dec2020 simplified syntax; string variables are encoded first * 1.0.1 30jun2020 aesthetic changes * 1.0.0 15apr2020 born program peerreview, sortpreserve byable(onecall) version 9.0 // Syntax syntax varname [if] [in] , Review(string) [by(varlist)] // By prefix if _by() { if "`by'" != "" { di as error "by prefix or by() option allowed, not both" exit 198 } else local by "`_byvars'" } // Preserve the data preserve // Sample to use if "`if'`in'" != "" marksample touse, strok else { tempvar touse qui gen byte `touse' = 1 } // Sort if "`by'" != "" { tempvar group bysort `by' (`_sortindex'): gen long `group' = _n == 1 qui replace `group' = sum(`group') } // Get number of reviews to be assigned and varname parse_name_opt `review' local reviews `s(integer)' local var_reviews `s(newvarname)' if "`var_reviews'" == "" local var_reviews "review" // Default // Check if variable is string or numeric cap confirm string variable `varlist' if _rc { local isstring 0 local var_reviewer `varlist' } else { local isstring 1 if _byindex() == 1 { tempvar var_reviewer encode `varlist', gen(`var_reviewer') } } // Check if variables to be created exist already if `reviews' == 1 { confirm new variable `var_reviews' if `isstring' { tempvar `var_reviews' local var_reviews_label ``var_reviews'' } } else { forval i = 1/`reviews' { confirm new variable `var_reviews'`i' if `isstring' { tempvar `var_reviews'`i' local var_reviews_label `var_reviews_label' ``var_reviews'`i'' } } } // Generate review variables if `reviews' == 1 { if `isstring' { qui gen ``var_reviews'' = . } else { qui gen `var_reviews' = . } } else { if `isstring' { forval i = 1/`reviews' { qui gen ``var_reviews'`i'' = . } } else { forval i = 1/`reviews' { qui gen `var_reviews'`i' = . } } } // Assign if "`by'" != "" { tempvar touse_group qui gen `touse_group' = . qui levelsof `group' if `touse', local(groups) if "`groups'" == "" error 2000 else { foreach g of local groups { qui replace `touse_group' = `touse' & `group' == `g' peerreview_assign "`var_reviewer'" "`touse_group'" "`reviews'" "`isstring'" "`var_reviews'" "`var_reviews_label'" "`g'" "`_sortindex'" "`varlist'" } } } else peerreview_assign "`var_reviewer'" "`touse'" "`reviews'" "`isstring'" "`var_reviews'" "`var_reviews_label'" "" "`_sortindex'" "`varlist'" // Decode review variables if string if `isstring' { lab val `var_reviews_label' `var_reviewer' if `reviews' == 1 decode ``var_reviews'', gen(`var_reviews') else { forval i = 1/`reviews' { decode ``var_reviews'`i'', gen(`var_reviews'`i') } } } restore, not end program peerreview_assign version 9 args var_reviewer touse reviews isstring var_reviews var_reviews_label group sortindex varlist // Sort gsort -`touse' `sortindex' // Store observations qui count if `touse' local reviewers = r(N) // Argument conditions qui tab `var_reviewer' if `touse' if `r(r)' != `reviewers' { di as error "duplicate values in variable {bf:`varlist'}" exit 499 } if `reviews' < 1 { di as error "number of reviews must be at least 1" exit 119 } if `reviewers' < 2 { if `reviewers' == 0 error 2000 else { di as error "number of reviewers must be at least 2" exit 119 } } if `reviewers' <= `reviews' { di as error "number of reviews must be smaller than number of reviewers" exit 119 } // Create list of reviews based on varname qui levelsof `var_reviewer' if `touse', local(levels) forval i = 1/`reviews' { local review_pool `review_pool' `levels' } // Make sure tempvars are referenced correctly for string variables if `isstring' { if `reviews' == 1 local `var_reviews' `var_reviews_label' else { forval i = 1/`reviews' { local `var_reviews'`i' `:word `i' of `var_reviews_label'' } } } // Mata: create inlist conditions and reviewer/author combination matrix tempname rvwrs_rvws_comb_mat mata : peerreview_comb_mat(`reviewers', `reviews', `isstring') // Shuffle list of reviews and assign to reviewers // Reviews are put at the end of the list if one of the conditions is not satisfied // In some cases, the reviews that are left cannot satisfy the conditions for the last couple of reviewers // If this is the case, the loop breaks and the list of reviews is reshuffled local iterations = 1 local counter = 1 while `counter' != `= `reviewers' * `reviews' + 1' { // Only false when succesfully assigned reviews to all reviewers // Randomize list of reviews and assignment order mata : peerreview_shuffle_comb_mat("`rvwrs_rvws_comb_mat'") mata : st_local("review_list", invtokens(jumble(tokens(st_local("review_pool"))')')) // Generate review variables if `reviews' == 1 { if `isstring' { qui replace ``var_reviews'' = . if `touse' } else { qui replace `var_reviews' = . if `touse' } } else { if `isstring' { forval i = 1/`reviews' { qui replace ``var_reviews'`i'' = . if `touse' } } else { forval i = 1/`reviews' { qui replace `var_reviews'`i' = . if `touse' } } } // Assign reviews to reviewers local counter = 1 local cond_not_satisf = 0 foreach rvw of local review_list { local i : word `counter' of `reviewer_nr' local j : word `counter' of `author_nr' if `var_reviewer'[`i'] == `rvw' | inlist(`rvw' `inlist_cond') { // Conditions: Reviewer cannot review themselves or review more than once local review_list `review_list' `rvw' // Add review to the end of the list local ++cond_not_satisf // Count times condition not satisfied if `cond_not_satisf' == `= `reviews' * 2 ' { local ++iterations continue, break // Break and reshuffle if condition is repeatedly not satisfied } continue } // Assign review to reviewer if `isstring' { if `reviews' == 1 qui replace ``var_reviews'' = `rvw' in `i' else qui replace ``var_reviews'`j'' = `rvw' in `i' } else { if `reviews' == 1 qui replace `var_reviews' = `rvw' in `i' else qui replace `var_reviews'`j' = `rvw' in `i' } // Reset count condition not satisfied local cond_not_satisf = 0 local ++counter } } if "`group'" == "" di as txt "assigned succesfully; iterations: " as res `iterations' else di as txt "-> group `group': assigned succesfully; iterations: " as res `iterations' end // Parser for options with name suboption program parse_name_opt, sclass version 9.0 syntax anything(id="integer") [, Name(name)] confirm integer number `anything' sreturn local integer `anything' sreturn local newvarname `name' end // Mata functions version 9.0 mata: // Create reviewers/reviews combination matrix and inlist conditions void peerreview_comb_mat(real scalar rvwrs, real scalar rvws, real scalar isstring) { // Create empty matrices inlist_mat = J(1, rvws, ".") rvwrs_rvws_comb_mat = J(rvws * rvwrs, 2, .) row_nr = 1 for (i=1; i<=rvws; i++) { // Create reviewers/reviews combination matrix for (j=1; j<=rvwrs; j++) { rvwrs_rvws_comb_mat[row_nr, 1] = j rvwrs_rvws_comb_mat[row_nr, 2] = i row_nr++ } // Create inlist conditions for inlist below (conditions are different for different number of reviews) if (rvws == 1) { if (isstring) inlist_mat[i] = ", \`\`var_reviews''" + "[\`i']" else inlist_mat[i] = ", \`var_reviews'" + "[\`i']" } else { if (isstring) inlist_mat[i] = ", \`\`var_reviews'" + strofreal(i) + "'[\`i']" else inlist_mat[i] = ", \`var_reviews'" + strofreal(i) + "[\`i']" } } st_local("inlist_cond", invtokens(inlist_mat)) st_matrix(st_local("rvwrs_rvws_comb_mat"), rvwrs_rvws_comb_mat) } // Shuffle reviewers/reviews combination matrix void peerreview_shuffle_comb_mat(string scalar matname) { comb_mat = strofreal(jumble(st_matrix(matname))) st_local("reviewer_nr", invtokens(comb_mat[1...,1]')) st_local("author_nr", invtokens(comb_mat[1...,2]')) } end