Regarding your request for papers, there is:
This isn't quite what you're looking for, but might serve as grist for the mill.
There is another strategy that no one seems to have mentioned. It is possible to generate $N-k$ (pseudo) random data out of a set of size $N$ such that whole set meets $k$ constraints so long as the remaining $k$ data are fixed at appropriate values. The required values should be solvable with a system of $k$ equations, algebra, and some elbow grease.
For example, to generate a set of $N$ data from a normal distribution that will have a given sample mean, $\bar x$, and variance, $s^2$, you will need to fix the values of two points: $y$ and $z$. Since the sample mean is:
$$
\bar x = \frac{\sum_{i=1}^{N-2}x_i\; + \;y\!+\!z}{N}
$$
$y$ must be:
$$
y = N\bar x\; - \;\left(\sum_{i=1}^{N-2}x_i\!+\!z\right)
$$
The sample variance is:
$$
s^2 = \frac{\sum_{i=1}^{N-2}(x_i - \bar x)^2\; + \;(y - \bar x)^2\!+\!(z - \bar x)^2}{N-1}
$$
thus (after substituting the above for $y$, foiling / distributing, & rearranging...) we get:
$$
2(N\bar{x}\! - \!\sum_{i=1}^{N-2}x_i)z - 2z^2 = N\bar{x}^2(N\!-\!1) + \sum_{i=1}^{N-2}x_i^2 + \left[\sum_{i=1}^{N-2}x_i\right]^2 - 2N\bar{x}\sum_{i=1}^{N-2}x_i - (N\!-\!1)s^2
$$
If we take $a=-2$, $b=2(N\bar{x} - \sum_{i=1}^{N-2}x_i)$, and $c$ as the negation of the RHS, we can solve for $z$ using the quadratic formula. For example, in R
, the following code could be used:
find.yz = function(x, xbar, s2){
N = length(x) + 2
sumx = sum(x)
sx2 = as.numeric(x%*%x) # this is the sum of x^2
a = -2
b = 2*(N*xbar - sumx)
c = -N*xbar^2*(N-1) - sx2 - sumx^2 + 2*N*xbar*sumx + (N-1)*s2
rt = sqrt(b^2 - 4*a*c)
z = (-b + rt)/(2*a)
y = N*xbar - (sumx + z)
newx = c(x, y, z)
return(newx)
}
set.seed(62)
x = rnorm(2)
newx = find.yz(x, xbar=0, s2=1)
newx # [1] 0.8012701 0.2844567 0.3757358 -1.4614627
mean(newx) # [1] 0
var(newx) # [1] 1
There are some things to understand about this approach. First, it's not guaranteed to work. For example, it is possible that your initial $N-2$ data are such that no values $y$ and $z$ exist that will make the variance of the resulting set equal $s^2$. Consider:
set.seed(22)
x = rnorm(2)
newx = find.yz(x, xbar=0, s2=1)
Warning message:
In sqrt(b^2 - 4 * a * c) : NaNs produced
newx # [1] -0.5121391 2.4851837 NaN NaN
var(c(x, mean(x), mean(x))) # [1] 1.497324
Second, whereas standardizing makes the marginal distributions of all your variates more uniform, this approach only affects the last two values, but makes their marginal distributions skewed:
set.seed(82)
xScaled = matrix(NA, ncol=4, nrow=10000)
for(i in 1:10000){
x = rnorm(4)
xScaled[i,] = scale(x)
}
set.seed(82)
xDf = matrix(NA, ncol=4, nrow=10000)
i = 1
while(i<10001){
x = rnorm(2)
xDf[i,] = try(find.yz(x, xbar=0, s2=2), silent=TRUE) # keeps the code from crashing
if(!is.nan(xDf[i,4])){ i = i+1 } # increments if worked
}
Third, the resulting sample may not look very normal; it might look like it has 'outliers' (i.e., points that come from a different data generating process than the rest), since that is essentially the case. This is less likely to be a problem with larger sample sizes, as the sample statistics from the generated data should converge to the required values and thus need less adjustment. With smaller samples, you could always combine this approach with an accept / reject algorithm that tries again if the generated sample has shape statistics (e.g., skewness and kurtosis) that are outside of acceptable bounds (cf., @cardinal's comment), or extend this approach to generate a sample with a fixed mean, variance, skewness, and kurtosis (I'll leave the algebra up to you, though). Alternatively, you could generate a small number of samples and use the one with the smallest (say) Kolmogorov-Smirnov statistic.
library(moments)
set.seed(7900)
x = rnorm(18)
newx.ss7900 = find.yz(x, xbar=0, s2=1)
skewness(newx.ss7900) # [1] 1.832733
kurtosis(newx.ss7900) - 3 # [1] 4.334414
ks.test(newx.ss7900, "pnorm")$statistic # 0.1934226
set.seed(200)
x = rnorm(18)
newx.ss200 = find.yz(x, xbar=0, s2=1)
skewness(newx.ss200) # [1] 0.137446
kurtosis(newx.ss200) - 3 # [1] 0.1148834
ks.test(newx.ss200, "pnorm")$statistic # 0.1326304
set.seed(4700)
x = rnorm(18)
newx.ss4700 = find.yz(x, xbar=0, s2=1)
skewness(newx.ss4700) # [1] 0.3258491
kurtosis(newx.ss4700) - 3 # [1] -0.02997377
ks.test(newx.ss4700, "pnorm")$statistic # 0.07707929S
x<-rnorm(72);x<-5.2*(x-mean(x))/sd(x)+102
does the trick. $\endgroup$