├── .gitignore ├── DOE_functions.py ├── Generate_DOE.py ├── Images ├── LHS example.PNG ├── LHSsampling.png ├── Params example frac fact.PNG ├── Params example full fact.PNG ├── Params example.PNG ├── Readme.md ├── Response_surface_metodology.jpg ├── User API Screenshot.PNG ├── factorial designs.jpg └── lamp-2799130_1280.jpg ├── LICENSE ├── Main.py ├── Notebooks ├── DOE_Analysis.ipynb └── Readme.md ├── README.md ├── Read_Write_CSV.py ├── SC-1.PNG ├── Setup.bash ├── Setup.bat ├── User_input.py ├── doe_box_behnken.py ├── doe_factorial.py ├── params.csv └── pyDOE_corrected.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | -------------------------------------------------------------------------------- /DOE_functions.py: -------------------------------------------------------------------------------- 1 | #==================== 2 | # Essential imports 3 | #==================== 4 | from pyDOE import * 5 | from pyDOE_corrected import * 6 | from diversipy import * 7 | import pandas as pd 8 | import numpy as np 9 | 10 | # =========================================================================================================== 11 | # Function for constructing a DataFrame from a numpy array generated by PyDOE function and individual lists 12 | # =========================================================================================================== 13 | 14 | def construct_df(x,r): 15 | df=pd.DataFrame(data=x,dtype='float32') 16 | for i in df.index: 17 | for j in range(len(list(df.iloc[i]))): 18 | df.iloc[i][j]=r[j][int(df.iloc[i][j])] 19 | return df 20 | 21 | # =================================================================================================== 22 | # Function for constructing a DataFrame from a matrix with floating point numbers between -1 and +1 23 | # =================================================================================================== 24 | 25 | def construct_df_from_matrix(x,factor_array): 26 | """ 27 | This function constructs a DataFrame out of x and factor_array, both of which are assumed to be numpy arrays. 28 | It projects the numbers in the x (which is output of a design-of-experiment build) to the factor array ranges. 29 | Here factor_array is assumed to have only min and max ranges. 30 | Matrix x is assumed to have numbers ranging from -1 to 1. 31 | """ 32 | 33 | row_num=x.shape[0] # Number of rows in the matrix x 34 | col_num=x.shape[1] # Number of columns in the matrix x 35 | 36 | empty=np.zeros((row_num,col_num)) 37 | 38 | def simple_substitution(idx,factor_list): 39 | if idx==-1: 40 | return factor_list[0] 41 | elif idx==0: 42 | return factor_list[1] 43 | elif idx==1: 44 | return factor_list[2] 45 | else: 46 | alpha=np.abs(factor_list[2]-factor_list[0])/2 47 | if idx<0: 48 | beta=np.abs(idx)-1 49 | return factor_list[0]-(beta*alpha) 50 | else: 51 | beta=idx-1 52 | return factor_list[2]+(beta*alpha) 53 | 54 | for i in range(row_num): 55 | for j in range(col_num): 56 | empty[i,j] = simple_substitution(x[i,j],factor_array[j]) 57 | 58 | return pd.DataFrame(data=empty) 59 | 60 | # ================================================================================================= 61 | # Function for constructing a DataFrame from a matrix with floating point numbers between 0 and 1 62 | # ================================================================================================= 63 | 64 | def construct_df_from_random_matrix(x,factor_array): 65 | """ 66 | This function constructs a DataFrame out of matrix x and factor_array, both of which are assumed to be numpy arrays. 67 | It projects the numbers in the x (which is output of a design-of-experiment build) to the factor array ranges. 68 | Here factor_array is assumed to have only min and max ranges. 69 | Matrix x is assumed to have numbers ranging from 0 to 1 only. 70 | """ 71 | 72 | row_num=x.shape[0] # Number of rows in the matrix x 73 | col_num=x.shape[1] # Number of columns in the matrix x 74 | 75 | empty=np.zeros((row_num,col_num)) 76 | 77 | def simple_substitution(idx,factor_list): 78 | alpha=np.abs(factor_list[1]-factor_list[0]) 79 | beta=idx 80 | return factor_list[0]+(beta*alpha) 81 | 82 | for i in range(row_num): 83 | for j in range(col_num): 84 | empty[i,j] = simple_substitution(x[i,j],factor_array[j]) 85 | 86 | return pd.DataFrame(data=empty) 87 | 88 | # ====================================================================================== 89 | # Function for building full factorial DataFrame from a dictionary of process variables 90 | # ====================================================================================== 91 | 92 | def build_full_fact(factor_level_ranges): 93 | """ 94 | Builds a full factorial design dataframe from a dictionary of factor/level ranges 95 | Example of the process variable dictionary: 96 | {'Pressure':[50,60,70],'Temperature':[290, 320, 350],'Flow rate':[0.9,1.0]} 97 | """ 98 | 99 | factor_lvl_count=[] 100 | factor_lists=[] 101 | 102 | for key in factor_level_ranges: 103 | factor_lvl_count.append(len(factor_level_ranges[key])) 104 | factor_lists.append(factor_level_ranges[key]) 105 | 106 | x = fullfact_corrected(factor_lvl_count) 107 | df=construct_df(x,factor_lists) 108 | df.columns=factor_level_ranges.keys() 109 | 110 | return df 111 | 112 | # ================================================================================================================================================== 113 | # Function for building 2-level fractional factorial DataFrame from a dictionary and a generator string 114 | # ================================================================================================================================================================ 115 | 116 | def build_frac_fact(factor_level_ranges,gen_string): 117 | """ 118 | Builds a full factorial design dataframe from a dictionary of factor/level ranges. 119 | Only min and max values of the range are required. 120 | Example of the dictionary: 121 | {'Pressure':[50,70],'Temperature':[290, 350],'Flow rate':[0.9,1.0]} 122 | 123 | This function requires a little more knowledge of how the confounding will be allowed. 124 | This means that some factor effects get muddled with other interaction effects, so it’s harder to distinguish between them). 125 | 126 | Let’s assume that we just can’t afford (for whatever reason) the number of runs in a full-factorial design. We can systematically decide on a fraction of the full-factorial by allowing some of the factor main effects to be confounded with other factor interaction effects. 127 | This is done by defining an alias structure that defines, symbolically, these interactions. These alias structures are written like “C = AB” or “I = ABC”, or “AB = CD”, etc. 128 | These define how one column is related to the others. 129 | 130 | EXAMPLE 131 | ------------ 132 | For example, the alias “C = AB” or “I = ABC” indicate that there are three factors (A, B, and C) and that the main effect of factor C is confounded with the interaction effect of the product AB, and by extension, A is confounded with BC and B is confounded with AC. 133 | A full- factorial design with these three factors results in a design matrix with 8 runs, but we will assume that we can only afford 4 of those runs. 134 | To create this fractional design, we need a matrix with three columns, one for A, B, and C, only now where the levels in the C column is created by the product of the A and B columns. 135 | """ 136 | 137 | factor_count=len(factor_level_ranges) 138 | factor_lists=[] 139 | 140 | for key in factor_level_ranges: 141 | if len(factor_level_ranges[key])!=2: 142 | factor_level_ranges[key][1]=factor_level_ranges[key][-1] 143 | factor_level_ranges[key]=factor_level_ranges[key][:2] 144 | print(f"{key} had more than two levels. Assigning the end point to the high level.") 145 | 146 | if factor_count!=len(gen_string.split(' ')): 147 | print("Length of the generator string for the fractional factorial build does not match the length of the process variables dictionary") 148 | return None 149 | 150 | for key in factor_level_ranges: 151 | factor_lists.append(factor_level_ranges[key]) 152 | 153 | x = fracfact(gen_string) 154 | 155 | def index_change(x): 156 | if x==-1: 157 | return 0 158 | else: 159 | return x 160 | vfunc=np.vectorize(index_change) 161 | x=vfunc(x) 162 | 163 | df=construct_df(x,factor_lists) 164 | df.columns=factor_level_ranges.keys() 165 | 166 | return df 167 | 168 | # ===================================================================================== 169 | # Function for building Plackett-Burman designs from a dictionary of process variables 170 | # ===================================================================================== 171 | 172 | def build_plackett_burman(factor_level_ranges): 173 | """ 174 | Builds a Plackett-Burman dataframe from a dictionary of factor/level ranges. 175 | Only min and max values of the range are required. 176 | Example of the dictionary: 177 | {'Pressure':[50,70],'Temperature':[290, 350],'Flow rate':[0.9,1.0]} 178 | 179 | Plackett–Burman designs are experimental designs presented in 1946 by Robin L. Plackett and J. P. Burman while working in the British Ministry of Supply.(Their goal was to find experimental designs for investigating the dependence of some measured quantity on a number of independent variables (factors), each taking L levels, in such a way as to minimize the variance of the estimates of these dependencies using a limited number of experiments. 180 | 181 | Interactions between the factors were considered negligible. The solution to this problem is to find an experimental design where each combination of levels for any pair of factors appears the same number of times, throughout all the experimental runs (refer to table). 182 | A complete factorial design would satisfy this criterion, but the idea was to find smaller designs. 183 | 184 | These designs are unique in that the number of trial conditions (rows) expands by multiples of four (e.g. 4, 8, 12, etc.). 185 | The max number of columns allowed before a design increases the number of rows is always one less than the next higher multiple of four. 186 | """ 187 | 188 | for key in factor_level_ranges: 189 | if len(factor_level_ranges[key])!=2: 190 | factor_level_ranges[key][1]=factor_level_ranges[key][-1] 191 | factor_level_ranges[key]=factor_level_ranges[key][:2] 192 | print(f"{key} had more than two levels. Assigning the end point to the high level.") 193 | 194 | factor_count=len(factor_level_ranges) 195 | factor_lists=[] 196 | 197 | for key in factor_level_ranges: 198 | factor_lists.append(factor_level_ranges[key]) 199 | 200 | x = pbdesign(factor_count) 201 | 202 | def index_change(x): 203 | if x==-1: 204 | return 0 205 | else: 206 | return x 207 | vfunc=np.vectorize(index_change) 208 | x=vfunc(x) 209 | 210 | df=construct_df(x,factor_lists) 211 | df.columns=factor_level_ranges.keys() 212 | 213 | return df 214 | 215 | # =================================================================================== 216 | # Function for building Sukharev Grid designs from a dictionary of process variables 217 | # =================================================================================== 218 | 219 | def build_sukharev(factor_level_ranges,num_samples=None): 220 | """ 221 | Builds a Sukharev-grid hypercube design dataframe from a dictionary of factor/level ranges. 222 | Number of samples raised to the power of (1/dimension), where dimension is the number of variables, must be an integer. 223 | Only min and max values of the range are required. 224 | Example of the dictionary: 225 | {'Pressure':[50,70],'Temperature':[290, 350],'Flow rate':[0.9,1.0]} 226 | num_samples: Number of samples to be generated 227 | 228 | Special property of this grid is that points are not placed on the boundaries of the hypercube, but at centroids of the subcells constituted by individual samples. 229 | This design offers optimal results for the covering radius regarding distances based on the max-norm. 230 | """ 231 | for key in factor_level_ranges: 232 | if len(factor_level_ranges[key])!=2: 233 | factor_level_ranges[key][1]=factor_level_ranges[key][-1] 234 | factor_level_ranges[key]=factor_level_ranges[key][:2] 235 | print(f"{key} had more than two levels. Assigning the end point to the high level.") 236 | 237 | factor_count=len(factor_level_ranges) 238 | factor_lists=[] 239 | 240 | for key in factor_level_ranges: 241 | factor_lists.append(factor_level_ranges[key]) 242 | 243 | check=num_samples**((1/factor_count)) 244 | if (check-int(check)>1e-5): 245 | num_samples=(int(check)+1)**(factor_count) 246 | print("\nNumber of samples not adequate to fill a Sukharev grid. Increasing sample size to: ",num_samples) 247 | 248 | x = sukharev_grid(num_points=num_samples,dimension=factor_count) 249 | factor_lists=np.array(factor_lists) 250 | 251 | df = construct_df_from_random_matrix(x,factor_lists) 252 | df.columns=factor_level_ranges.keys() 253 | return df 254 | 255 | # =================================================================================== 256 | # Function for building Box-Behnken designs from a dictionary of process variables 257 | # =================================================================================== 258 | 259 | def build_box_behnken(factor_level_ranges,center=1): 260 | """ 261 | Builds a Box-Behnken design dataframe from a dictionary of factor/level ranges. 262 | Note 3 levels of factors are necessary. If not given, the function will automatically create 3 levels by linear mid-section method. 263 | Example of the dictionary: 264 | {'Pressure':[50,60,70],'Temperature':[290, 320, 350],'Flow rate':[0.9,1.0,1.1]} 265 | 266 | In statistics, Box–Behnken designs are experimental designs for response surface methodology, devised by George E. P. Box and Donald Behnken in 1960, to achieve the following goals: 267 | * Each factor, or independent variable, is placed at one of three equally spaced values, usually coded as −1, 0, +1. (At least three levels are needed for the following goal.) 268 | * The design should be sufficient to fit a quadratic model, that is, one containing squared terms, products of two factors, linear terms and an intercept. 269 | * The ratio of the number of experimental points to the number of coefficients in the quadratic model should be reasonable (in fact, their designs kept it in the range of 1.5 to 2.6).*estimation variance should more or less depend only on the distance from the centre (this is achieved exactly for the designs with 4 and 7 factors), and should not vary too much inside the smallest (hyper)cube containing the experimental points. 270 | """ 271 | for key in factor_level_ranges: 272 | if len(factor_level_ranges[key])==2: 273 | factor_level_ranges[key].append((factor_level_ranges[key][0]+factor_level_ranges[key][1])/2) 274 | factor_level_ranges[key].sort() 275 | print(f"{key} had only two end points. Creating a mid-point by averaging them") 276 | 277 | factor_count=len(factor_level_ranges) 278 | factor_lists=[] 279 | 280 | for key in factor_level_ranges: 281 | factor_lists.append(factor_level_ranges[key]) 282 | 283 | x = bbdesign_corrected(factor_count,center=center) 284 | x=x+1 #Adjusting the index up by 1 285 | 286 | df=construct_df(x,factor_lists) 287 | df.columns=factor_level_ranges.keys() 288 | 289 | return df 290 | 291 | # ===================================================================================================== 292 | # Function for building central-composite (Box-Wilson) designs from a dictionary of process variables 293 | # ===================================================================================================== 294 | 295 | def build_central_composite(factor_level_ranges,center=(2,2),alpha='o',face='ccc'): 296 | """ 297 | Builds a central-composite design dataframe from a dictionary of factor/level ranges. 298 | Only min and max values of the range are required. 299 | Example of the dictionary: 300 | {'Pressure':[50,70],'Temperature':[290, 350],'Flow rate':[0.9,1.0]} 301 | 302 | In statistics, a central composite design is an experimental design, useful in response surface methodology, for building a second order (quadratic) model for the response variable without needing to use a complete three-level factorial experiment. 303 | The design consists of three distinct sets of experimental runs: 304 | * A factorial (perhaps fractional) design in the factors studied, each having two levels; 305 | * A set of center points, experimental runs whose values of each factor are the medians of the values used in the factorial portion. This point is often replicated in order to improve the precision of the experiment; 306 | * A set of axial points, experimental runs identical to the centre points except for one factor, which will take on values both below and above the median of the two factorial levels, and typically both outside their range. All factors are varied in this way. 307 | """ 308 | for key in factor_level_ranges: 309 | if len(factor_level_ranges[key])!=2: 310 | factor_level_ranges[key][1]=factor_level_ranges[key][-1] 311 | factor_level_ranges[key]=factor_level_ranges[key][:2] 312 | print(f"{key} had more than two levels. Assigning the end point to the high level.") 313 | 314 | 315 | # Creates the mid-points by averaging the low and high levels 316 | for key in factor_level_ranges: 317 | if len(factor_level_ranges[key])==2: 318 | factor_level_ranges[key].append((factor_level_ranges[key][0]+factor_level_ranges[key][1])/2) 319 | factor_level_ranges[key].sort() 320 | 321 | factor_count=len(factor_level_ranges) 322 | factor_lists=[] 323 | 324 | for key in factor_level_ranges: 325 | factor_lists.append(factor_level_ranges[key]) 326 | 327 | x = ccdesign(factor_count,center=center,alpha=alpha,face=face) 328 | factor_lists=np.array(factor_lists) 329 | 330 | df = construct_df_from_matrix(x,factor_lists) 331 | df.columns=factor_level_ranges.keys() 332 | return df 333 | 334 | # ==================================================================================== 335 | # Function for building simple Latin Hypercube from a dictionary of process variables 336 | # ==================================================================================== 337 | 338 | def build_lhs(factor_level_ranges, num_samples=None, prob_distribution=None): 339 | """ 340 | Builds a Latin Hypercube design dataframe from a dictionary of factor/level ranges. 341 | Only min and max values of the range are required. 342 | Example of the dictionary: 343 | {'Pressure':[50,70],'Temperature':[290, 350],'Flow rate':[0.9,1.0]} 344 | num_samples: Number of samples to be generated 345 | prob_distribution: Analytical probability distribution to be applied over the randomized sampling. 346 | Takes strings like: 'Normal', 'Poisson', 'Exponential', 'Beta', 'Gamma' 347 | 348 | Latin hypercube sampling (LHS) is a form of stratified sampling that can be applied to multiple variables. The method commonly used to reduce the number or runs necessary for a Monte Carlo simulation to achieve a reasonably accurate random distribution. LHS can be incorporated into an existing Monte Carlo model fairly easily, and work with variables following any analytical probability distribution. 349 | """ 350 | for key in factor_level_ranges: 351 | if len(factor_level_ranges[key])!=2: 352 | factor_level_ranges[key][1]=factor_level_ranges[key][-1] 353 | factor_level_ranges[key]=factor_level_ranges[key][:2] 354 | print(f"{key} had more than two levels. Assigning the end point to the high level.") 355 | 356 | factor_count=len(factor_level_ranges) 357 | factor_lists=[] 358 | 359 | if num_samples==None: 360 | num_samples=factor_count 361 | 362 | for key in factor_level_ranges: 363 | factor_lists.append(factor_level_ranges[key]) 364 | 365 | x = lhs(n=factor_count,samples=num_samples) 366 | factor_lists=np.array(factor_lists) 367 | 368 | df = construct_df_from_random_matrix(x,factor_lists) 369 | df.columns=factor_level_ranges.keys() 370 | return df 371 | 372 | # ============================================================================================ 373 | # Function for building space-filling Latin Hypercube from a dictionary of process variables 374 | # ============================================================================================ 375 | 376 | def build_space_filling_lhs(factor_level_ranges, num_samples=None): 377 | """ 378 | Builds a space-filling Latin Hypercube design dataframe from a dictionary of factor/level ranges. 379 | Only min and max values of the range are required. 380 | Example of the dictionary: 381 | {'Pressure':[50,70],'Temperature':[290, 350],'Flow rate':[0.9,1.0]} 382 | num_samples: Number of samples to be generated 383 | """ 384 | for key in factor_level_ranges: 385 | if len(factor_level_ranges[key])!=2: 386 | factor_level_ranges[key][1]=factor_level_ranges[key][-1] 387 | factor_level_ranges[key]=factor_level_ranges[key][:2] 388 | print(f"{key} had more than two levels. Assigning the end point to the high level.") 389 | 390 | factor_count=len(factor_level_ranges) 391 | factor_lists=[] 392 | 393 | if num_samples==None: 394 | num_samples=factor_count 395 | 396 | for key in factor_level_ranges: 397 | factor_lists.append(factor_level_ranges[key]) 398 | 399 | x = transform_spread_out(lhd_matrix(num_points=num_samples,dimension=factor_count)) # create latin hypercube design 400 | factor_lists=np.array(factor_lists) 401 | 402 | df = construct_df_from_random_matrix(x,factor_lists) 403 | df.columns=factor_level_ranges.keys() 404 | return df 405 | 406 | # ===================================================================================================== 407 | # Function for building designs with random _k-means_ clusters from a dictionary of process variables 408 | # ===================================================================================================== 409 | 410 | def build_random_k_means(factor_level_ranges, num_samples=None): 411 | """ 412 | This function aims to produce a centroidal Voronoi tesselation of the unit random hypercube and generate k-means clusters. 413 | Only min and max values of the range are required. 414 | Example of the dictionary: 415 | {'Pressure':[50,70],'Temperature':[290, 350],'Flow rate':[0.9,1.0]} 416 | num_samples: Number of samples to be generated 417 | """ 418 | for key in factor_level_ranges: 419 | if len(factor_level_ranges[key])!=2: 420 | factor_level_ranges[key][1]=factor_level_ranges[key][-1] 421 | factor_level_ranges[key]=factor_level_ranges[key][:2] 422 | print(f"{key} had more than two levels. Assigning the end point to the high level.") 423 | 424 | factor_count=len(factor_level_ranges) 425 | factor_lists=[] 426 | 427 | if num_samples==None: 428 | num_samples=factor_count 429 | 430 | for key in factor_level_ranges: 431 | factor_lists.append(factor_level_ranges[key]) 432 | 433 | x = random_k_means(num_points=num_samples,dimension=factor_count) # create latin hypercube design 434 | factor_lists=np.array(factor_lists) 435 | 436 | df = construct_df_from_random_matrix(x,factor_lists) 437 | df.columns=factor_level_ranges.keys() 438 | return df 439 | 440 | # ============================================================================================= 441 | # Function for building maximin reconstruction matrix from a dictionary of process variables 442 | # ============================================================================================= 443 | 444 | def build_maximin(factor_level_ranges, num_samples=None): 445 | """ 446 | Builds a maximin reconstructed design dataframe from a dictionary of factor/level ranges. 447 | Only min and max values of the range are required. 448 | Example of the dictionary: 449 | {'Pressure':[50,70],'Temperature':[290, 350],'Flow rate':[0.9,1.0]} 450 | num_samples: Number of samples to be generated 451 | 452 | This algorithm carries out a user-specified number of iterations to maximize the minimal distance of a point in the set to 453 | * other points in the set, 454 | * existing (fixed) points, 455 | * the boundary of the hypercube. 456 | """ 457 | for key in factor_level_ranges: 458 | if len(factor_level_ranges[key])!=2: 459 | factor_level_ranges[key][1]=factor_level_ranges[key][-1] 460 | factor_level_ranges[key]=factor_level_ranges[key][:2] 461 | print(f"{key} had more than two levels. Assigning the end point to the high level.") 462 | 463 | factor_count=len(factor_level_ranges) 464 | factor_lists=[] 465 | 466 | if num_samples==None: 467 | num_samples=factor_count 468 | 469 | for key in factor_level_ranges: 470 | factor_lists.append(factor_level_ranges[key]) 471 | 472 | x = maximin_reconstruction(num_points=num_samples,dimension=factor_count) # create latin hypercube design 473 | factor_lists=np.array(factor_lists) 474 | 475 | df = construct_df_from_random_matrix(x,factor_lists) 476 | df.columns=factor_level_ranges.keys() 477 | return df 478 | 479 | # ======================================================================================== 480 | # Function for building Halton matrix based design from a dictionary of process variables 481 | # ======================================================================================== 482 | 483 | def build_halton(factor_level_ranges, num_samples=None): 484 | """ 485 | Builds a quasirandom dataframe from a dictionary of factor/level ranges using prime numbers as seed. 486 | Only min and max values of the range are required. 487 | Example of the dictionary: 488 | {'Pressure':[50,70],'Temperature':[290, 350],'Flow rate':[0.9,1.0]} 489 | num_samples: Number of samples to be generated 490 | 491 | Quasirandom sequence using the default initialization with first n prime numbers equal to the number of factors/variables. 492 | """ 493 | for key in factor_level_ranges: 494 | if len(factor_level_ranges[key])!=2: 495 | factor_level_ranges[key][1]=factor_level_ranges[key][-1] 496 | factor_level_ranges[key]=factor_level_ranges[key][:2] 497 | print(f"{key} had more than two levels. Assigning the end point to the high level.") 498 | 499 | factor_count=len(factor_level_ranges) 500 | factor_lists=[] 501 | 502 | if num_samples==None: 503 | num_samples=factor_count 504 | 505 | for key in factor_level_ranges: 506 | factor_lists.append(factor_level_ranges[key]) 507 | 508 | x = halton(num_points=num_samples,dimension=factor_count) # create Halton matrix design 509 | factor_lists=np.array(factor_lists) 510 | 511 | df = construct_df_from_random_matrix(x,factor_lists) 512 | df.columns=factor_level_ranges.keys() 513 | return df 514 | 515 | # ========================================================================================== 516 | # Function for building uniform random design matrix from a dictionary of process variables 517 | # ========================================================================================== 518 | 519 | def build_uniform_random (factor_level_ranges, num_samples=None): 520 | """ 521 | Builds a design dataframe with samples drawn from uniform random distribution based on a dictionary of factor/level ranges. 522 | Only min and max values of the range are required. 523 | Example of the dictionary: 524 | {'Pressure':[50,70],'Temperature':[290, 350],'Flow rate':[0.9,1.0]} 525 | num_samples: Number of samples to be generated 526 | """ 527 | for key in factor_level_ranges: 528 | if len(factor_level_ranges[key])!=2: 529 | factor_level_ranges[key][1]=factor_level_ranges[key][-1] 530 | factor_level_ranges[key]=factor_level_ranges[key][:2] 531 | print(f"{key} had more than two levels. Assigning the end point to the high level.") 532 | 533 | factor_count=len(factor_level_ranges) 534 | factor_lists=[] 535 | 536 | if num_samples==None: 537 | num_samples=factor_count 538 | 539 | for key in factor_level_ranges: 540 | factor_lists.append(factor_level_ranges[key]) 541 | 542 | x = random_uniform(num_points=num_samples,dimension=factor_count) # create Halton matrix design 543 | factor_lists=np.array(factor_lists) 544 | 545 | df = construct_df_from_random_matrix(x,factor_lists) 546 | df.columns=factor_level_ranges.keys() 547 | return df 548 | -------------------------------------------------------------------------------- /Generate_DOE.py: -------------------------------------------------------------------------------- 1 | from DOE_functions import * 2 | from Read_Write_CSV import * 3 | 4 | # ==================================================================== 5 | # Function to generate the DOE based on user's choice and input file 6 | # ==================================================================== 7 | 8 | def generate_DOE(doe_choice, infile): 9 | """ 10 | Generates the output design-of-experiment matrix by calling the appropriate function from the "DOE_function.py file". 11 | Returns the generated DataFrame (Pandas) and a filename (string) corresponding to the type of the DOE sought by the user. This filename string is used by the CSV writer function to write to the disk i.e. save the generated DataFrame in a CSV format. 12 | """ 13 | 14 | dict_vars = read_variables_csv(infile) 15 | if type(dict_vars)!=int: 16 | factor_count=len(dict_vars) 17 | else: 18 | return (-1,-1) 19 | 20 | if doe_choice==1: 21 | df=build_full_fact(dict_vars) 22 | filename='Full_factorial_design' 23 | 24 | elif doe_choice==2: 25 | print("For this choice, you will be asked to enter a generator string expression. Please only use small letters e.g. 'a b c bc' for the string. Make sure to put a space in between every variable. Please note that the number of character blocks must be identical to the number of factors you have in your input file.\n") 26 | gen_string=str(input("Please enter the generator string for the fractional factorial build: ")) 27 | print() 28 | if len(gen_string.split(' '))!=factor_count: 29 | print("Length of the generator string does not match the number of factors/variables. Sorry!") 30 | return (-1,-1) 31 | df=build_frac_fact(dict_vars,gen_string) 32 | filename='Fractional_factorial_design' 33 | 34 | elif doe_choice==3: 35 | df=build_plackett_burman(dict_vars) 36 | filename='Plackett_Burman_design' 37 | 38 | elif doe_choice==4: 39 | num_samples=int(input("Please enter the number of samples: ")) 40 | print() 41 | df=build_sukharev(dict_vars,num_samples) 42 | filename='Sukharev_grid_design' 43 | 44 | elif doe_choice==5: 45 | num_center=int(input("Please enter the number of center points to be repeated (if more than one): ")) 46 | print() 47 | df=build_box_behnken(dict_vars,num_center) 48 | filename='Box_Behnken_design' 49 | 50 | elif doe_choice==6: 51 | #num_center=int(input("Please enter the number of center points to be repeated (if more than one): ")) 52 | print() 53 | df=build_central_composite(dict_vars,face='ccf') 54 | filename='Box_Wilson_face_centered_design' 55 | 56 | elif doe_choice==7: 57 | #num_center=int(input("Please enter the number of center points to be repeated (if more than one): ")) 58 | print() 59 | df=build_central_composite(dict_vars,face='cci') 60 | filename='Box_Wilson_face_inscribed_design' 61 | 62 | elif doe_choice==8: 63 | #num_center=int(input("Please enter the number of center points to be repeated (if more than one): ")) 64 | print() 65 | df=build_central_composite(dict_vars,face='ccc') 66 | filename='Box_Wilson_face_circumscribed_design' 67 | 68 | elif doe_choice==9: 69 | num_samples=int(input("Please enter the number of random sample points to generate: ")) 70 | print() 71 | df=build_lhs(dict_vars,num_samples=num_samples) 72 | filename='Simple_Latin_Hypercube_design' 73 | 74 | elif doe_choice==10: 75 | num_samples=int(input("Please enter the number of random sample points to generate: ")) 76 | print() 77 | df=build_space_filling_lhs(dict_vars,num_samples=num_samples) 78 | filename='Space_filling_Latin_Hypercube_design' 79 | 80 | elif doe_choice==11: 81 | num_samples=int(input("Please enter the number of random sample points to generate: ")) 82 | print() 83 | df=build_random_k_means(dict_vars,num_samples=num_samples) 84 | filename='Random_k_means_design' 85 | 86 | elif doe_choice==12: 87 | num_samples=int(input("Please enter the number of random sample points to generate: ")) 88 | print() 89 | df=build_maximin(dict_vars,num_samples=num_samples) 90 | filename='Maximin_reconstruction_design' 91 | 92 | elif doe_choice==13: 93 | num_samples=int(input("Please enter the number of random sample points to generate: ")) 94 | print() 95 | df=build_halton(dict_vars,num_samples=num_samples) 96 | filename='Halton_sequence_design' 97 | 98 | elif doe_choice==14: 99 | num_samples=int(input("Please enter the number of random sample points to generate: ")) 100 | print() 101 | df=build_uniform_random(dict_vars,num_samples=num_samples) 102 | filename='Uniform_random_matrix_design' 103 | 104 | return (df,filename) -------------------------------------------------------------------------------- /Images/LHS example.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Design-of-experiment-Python/fc1d00b9525e7e583153727a8979b9427122a3e4/Images/LHS example.PNG -------------------------------------------------------------------------------- /Images/LHSsampling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Design-of-experiment-Python/fc1d00b9525e7e583153727a8979b9427122a3e4/Images/LHSsampling.png -------------------------------------------------------------------------------- /Images/Params example frac fact.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Design-of-experiment-Python/fc1d00b9525e7e583153727a8979b9427122a3e4/Images/Params example frac fact.PNG -------------------------------------------------------------------------------- /Images/Params example full fact.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Design-of-experiment-Python/fc1d00b9525e7e583153727a8979b9427122a3e4/Images/Params example full fact.PNG -------------------------------------------------------------------------------- /Images/Params example.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Design-of-experiment-Python/fc1d00b9525e7e583153727a8979b9427122a3e4/Images/Params example.PNG -------------------------------------------------------------------------------- /Images/Readme.md: -------------------------------------------------------------------------------- 1 | ## Images stored 2 | -------------------------------------------------------------------------------- /Images/Response_surface_metodology.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Design-of-experiment-Python/fc1d00b9525e7e583153727a8979b9427122a3e4/Images/Response_surface_metodology.jpg -------------------------------------------------------------------------------- /Images/User API Screenshot.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Design-of-experiment-Python/fc1d00b9525e7e583153727a8979b9427122a3e4/Images/User API Screenshot.PNG -------------------------------------------------------------------------------- /Images/factorial designs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Design-of-experiment-Python/fc1d00b9525e7e583153727a8979b9427122a3e4/Images/factorial designs.jpg -------------------------------------------------------------------------------- /Images/lamp-2799130_1280.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Design-of-experiment-Python/fc1d00b9525e7e583153727a8979b9427122a3e4/Images/lamp-2799130_1280.jpg -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tirthajyoti Sarkar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Main.py: -------------------------------------------------------------------------------- 1 | from Read_Write_CSV import * 2 | from Generate_DOE import * 3 | from User_input import * 4 | 5 | import time 6 | 7 | # ======================== 8 | # Main execution function 9 | # ======================== 10 | 11 | def execute_main(): 12 | """ 13 | Main function to execute the program. 14 | Calls "user_input" function to receive the choice of the DOE user wants to build and to read the input CSV file with the ranges of the variables. Thereafter, it calls the "generate_DOE" function to generate the DOE matrix and a suitable filename corresponding to the user's DOE choice. Finally, it calls the "write_CSV" function to write the DOE matrix (a Pandas DataFrame object) into a CSV file on the disk, and prints a message indicating the filename. 15 | """ 16 | doe_choice, infile = user_input() 17 | df, filename = generate_DOE(doe_choice,infile) 18 | if type(df)!=int or type(filename)!=int: 19 | flag=write_csv(df,filename) 20 | if flag!=-1: 21 | print("\nAnalyzing input and building the DOE...",end=' ') 22 | time.sleep(2) 23 | print("DONE!!") 24 | time.sleep(0.5) 25 | print(f"Output has been written to the file: {filename}.csv") 26 | else: 27 | print("\nError in writing the output. \nIf you have a file open with same filename, please close it before running the command again!") 28 | 29 | 30 | #===================================================== 31 | # Main UX with simple information about the software 32 | #===================================================== 33 | 34 | print() 35 | print(" "*5+"Design-of-experiment builder by Dr. Tirthajyoti Sarkar, ON Semiconductor"+" "*5) 36 | print(" "*20+"June 2018, Sunnyvale, CA 94086"+" "*20) 37 | print(" "*10+"Uses the following packages: numpy, pandas, pydoe, diversipy"+" "*10) 38 | print() 39 | 40 | # Executes the main function 41 | execute_main() -------------------------------------------------------------------------------- /Notebooks/DOE_Analysis.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 48, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from DOE_functions import *\n", 10 | "import numpy as np\n", 11 | "import pandas as pd\n", 12 | "import seaborn as sns\n", 13 | "import matplotlib.pyplot as plt" 14 | ] 15 | }, 16 | { 17 | "cell_type": "code", 18 | "execution_count": 86, 19 | "metadata": {}, 20 | "outputs": [], 21 | "source": [ 22 | "df=build_full_fact({'Pressure':[50,60,70],'Temperature':[290, 320, 350],'Flow rate':[0.9,1.0,1.1]})" 23 | ] 24 | }, 25 | { 26 | "cell_type": "code", 27 | "execution_count": 87, 28 | "metadata": {}, 29 | "outputs": [ 30 | { 31 | "data": { 32 | "text/html": [ 33 | "
\n", 34 | "\n", 47 | "\n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | " \n", 99 | " \n", 100 | " \n", 101 | " \n", 102 | " \n", 103 | " \n", 104 | " \n", 105 | " \n", 106 | " \n", 107 | " \n", 108 | " \n", 109 | " \n", 110 | " \n", 111 | " \n", 112 | " \n", 113 | " \n", 114 | " \n", 115 | " \n", 116 | " \n", 117 | " \n", 118 | " \n", 119 | " \n", 120 | " \n", 121 | " \n", 122 | " \n", 123 | " \n", 124 | " \n", 125 | " \n", 126 | " \n", 127 | " \n", 128 | " \n", 129 | " \n", 130 | " \n", 131 | " \n", 132 | " \n", 133 | " \n", 134 | " \n", 135 | " \n", 136 | " \n", 137 | " \n", 138 | " \n", 139 | " \n", 140 | " \n", 141 | " \n", 142 | " \n", 143 | " \n", 144 | " \n", 145 | " \n", 146 | " \n", 147 | " \n", 148 | " \n", 149 | " \n", 150 | " \n", 151 | " \n", 152 | " \n", 153 | " \n", 154 | " \n", 155 | " \n", 156 | " \n", 157 | " \n", 158 | " \n", 159 | " \n", 160 | " \n", 161 | " \n", 162 | " \n", 163 | " \n", 164 | " \n", 165 | " \n", 166 | " \n", 167 | " \n", 168 | " \n", 169 | " \n", 170 | " \n", 171 | " \n", 172 | " \n", 173 | " \n", 174 | " \n", 175 | " \n", 176 | " \n", 177 | " \n", 178 | " \n", 179 | " \n", 180 | " \n", 181 | " \n", 182 | " \n", 183 | " \n", 184 | " \n", 185 | " \n", 186 | " \n", 187 | " \n", 188 | " \n", 189 | " \n", 190 | " \n", 191 | " \n", 192 | " \n", 193 | " \n", 194 | " \n", 195 | " \n", 196 | " \n", 197 | " \n", 198 | " \n", 199 | " \n", 200 | " \n", 201 | " \n", 202 | " \n", 203 | " \n", 204 | " \n", 205 | " \n", 206 | " \n", 207 | " \n", 208 | " \n", 209 | " \n", 210 | " \n", 211 | " \n", 212 | " \n", 213 | " \n", 214 | " \n", 215 | " \n", 216 | " \n", 217 | " \n", 218 | " \n", 219 | " \n", 220 | "
PressureTemperatureFlow rate
050.0290.00.9
160.0290.00.9
270.0290.00.9
350.0320.00.9
460.0320.00.9
570.0320.00.9
650.0350.00.9
760.0350.00.9
870.0350.00.9
950.0290.01.0
1060.0290.01.0
1170.0290.01.0
1250.0320.01.0
1360.0320.01.0
1470.0320.01.0
1550.0350.01.0
1660.0350.01.0
1770.0350.01.0
1850.0290.01.1
1960.0290.01.1
2070.0290.01.1
2150.0320.01.1
2260.0320.01.1
2370.0320.01.1
2450.0350.01.1
2560.0350.01.1
2670.0350.01.1
\n", 221 | "
" 222 | ], 223 | "text/plain": [ 224 | " Pressure Temperature Flow rate\n", 225 | "0 50.0 290.0 0.9\n", 226 | "1 60.0 290.0 0.9\n", 227 | "2 70.0 290.0 0.9\n", 228 | "3 50.0 320.0 0.9\n", 229 | "4 60.0 320.0 0.9\n", 230 | "5 70.0 320.0 0.9\n", 231 | "6 50.0 350.0 0.9\n", 232 | "7 60.0 350.0 0.9\n", 233 | "8 70.0 350.0 0.9\n", 234 | "9 50.0 290.0 1.0\n", 235 | "10 60.0 290.0 1.0\n", 236 | "11 70.0 290.0 1.0\n", 237 | "12 50.0 320.0 1.0\n", 238 | "13 60.0 320.0 1.0\n", 239 | "14 70.0 320.0 1.0\n", 240 | "15 50.0 350.0 1.0\n", 241 | "16 60.0 350.0 1.0\n", 242 | "17 70.0 350.0 1.0\n", 243 | "18 50.0 290.0 1.1\n", 244 | "19 60.0 290.0 1.1\n", 245 | "20 70.0 290.0 1.1\n", 246 | "21 50.0 320.0 1.1\n", 247 | "22 60.0 320.0 1.1\n", 248 | "23 70.0 320.0 1.1\n", 249 | "24 50.0 350.0 1.1\n", 250 | "25 60.0 350.0 1.1\n", 251 | "26 70.0 350.0 1.1" 252 | ] 253 | }, 254 | "execution_count": 87, 255 | "metadata": {}, 256 | "output_type": "execute_result" 257 | } 258 | ], 259 | "source": [ 260 | "df" 261 | ] 262 | }, 263 | { 264 | "cell_type": "code", 265 | "execution_count": 95, 266 | "metadata": {}, 267 | "outputs": [], 268 | "source": [ 269 | "def gen_result(factors,randomness=0.05):\n", 270 | " \"\"\"\n", 271 | " factors: a numpy.series object i.e. a row of a DataFrame\n", 272 | " \"\"\"\n", 273 | " import numpy as np\n", 274 | " \n", 275 | " num_factors=len(factors)\n", 276 | " r=randomness\n", 277 | " variables=[]\n", 278 | " \n", 279 | " for i in range(num_factors):\n", 280 | " variables.append(factors[i])\n", 281 | " \n", 282 | " #result=0\n", 283 | " result = 1000*(variables[2]*(1+r*np.random.random()))**(2)*(variables[0]*(1+r*np.random.random()))/\\\n", 284 | " (variables[1]*(1+r*np.random.random())+variables[0]*(1+r*np.random.random()))\n", 285 | " \n", 286 | " return result" 287 | ] 288 | }, 289 | { 290 | "cell_type": "code", 291 | "execution_count": 89, 292 | "metadata": {}, 293 | "outputs": [ 294 | { 295 | "data": { 296 | "text/html": [ 297 | "
\n", 298 | "\n", 311 | "\n", 312 | " \n", 313 | " \n", 314 | " \n", 315 | " \n", 316 | " \n", 317 | " \n", 318 | " \n", 319 | " \n", 320 | " \n", 321 | " \n", 322 | " \n", 323 | " \n", 324 | " \n", 325 | " \n", 326 | " \n", 327 | " \n", 328 | " \n", 329 | " \n", 330 | " \n", 331 | " \n", 332 | " \n", 333 | " \n", 334 | " \n", 335 | " \n", 336 | " \n", 337 | " \n", 338 | " \n", 339 | " \n", 340 | " \n", 341 | " \n", 342 | " \n", 343 | " \n", 344 | " \n", 345 | " \n", 346 | " \n", 347 | " \n", 348 | " \n", 349 | " \n", 350 | " \n", 351 | " \n", 352 | " \n", 353 | " \n", 354 | " \n", 355 | " \n", 356 | " \n", 357 | " \n", 358 | " \n", 359 | " \n", 360 | " \n", 361 | " \n", 362 | " \n", 363 | " \n", 364 | " \n", 365 | " \n", 366 | " \n", 367 | " \n", 368 | " \n", 369 | " \n", 370 | " \n", 371 | " \n", 372 | " \n", 373 | " \n", 374 | " \n", 375 | " \n", 376 | " \n", 377 | " \n", 378 | " \n", 379 | " \n", 380 | " \n", 381 | " \n", 382 | " \n", 383 | " \n", 384 | " \n", 385 | " \n", 386 | " \n", 387 | " \n", 388 | " \n", 389 | " \n", 390 | " \n", 391 | " \n", 392 | " \n", 393 | " \n", 394 | " \n", 395 | " \n", 396 | " \n", 397 | " \n", 398 | " \n", 399 | " \n", 400 | " \n", 401 | " \n", 402 | " \n", 403 | " \n", 404 | " \n", 405 | " \n", 406 | " \n", 407 | " \n", 408 | " \n", 409 | " \n", 410 | " \n", 411 | " \n", 412 | " \n", 413 | " \n", 414 | " \n", 415 | " \n", 416 | " \n", 417 | " \n", 418 | " \n", 419 | " \n", 420 | " \n", 421 | " \n", 422 | " \n", 423 | " \n", 424 | " \n", 425 | " \n", 426 | " \n", 427 | " \n", 428 | " \n", 429 | " \n", 430 | " \n", 431 | " \n", 432 | " \n", 433 | " \n", 434 | " \n", 435 | " \n", 436 | " \n", 437 | " \n", 438 | " \n", 439 | " \n", 440 | " \n", 441 | " \n", 442 | " \n", 443 | " \n", 444 | " \n", 445 | " \n", 446 | " \n", 447 | " \n", 448 | " \n", 449 | " \n", 450 | " \n", 451 | " \n", 452 | " \n", 453 | " \n", 454 | " \n", 455 | " \n", 456 | " \n", 457 | " \n", 458 | " \n", 459 | " \n", 460 | " \n", 461 | " \n", 462 | " \n", 463 | " \n", 464 | " \n", 465 | " \n", 466 | " \n", 467 | " \n", 468 | " \n", 469 | " \n", 470 | " \n", 471 | " \n", 472 | " \n", 473 | " \n", 474 | " \n", 475 | " \n", 476 | " \n", 477 | " \n", 478 | " \n", 479 | " \n", 480 | " \n", 481 | " \n", 482 | " \n", 483 | " \n", 484 | " \n", 485 | " \n", 486 | " \n", 487 | " \n", 488 | " \n", 489 | " \n", 490 | " \n", 491 | " \n", 492 | " \n", 493 | " \n", 494 | " \n", 495 | " \n", 496 | " \n", 497 | " \n", 498 | " \n", 499 | " \n", 500 | " \n", 501 | " \n", 502 | " \n", 503 | " \n", 504 | " \n", 505 | " \n", 506 | " \n", 507 | " \n", 508 | " \n", 509 | " \n", 510 | " \n", 511 | " \n", 512 | "
PressureTemperatureFlow rateresult
050.0290.00.9118.973619
160.0290.00.9143.953286
270.0290.00.9161.500565
350.0320.00.9110.895524
460.0320.00.9134.219954
570.0320.00.9151.631696
650.0350.00.9102.399310
760.0350.00.9118.265809
870.0350.00.9139.410702
950.0290.01.0153.581631
1060.0290.01.0171.824979
1170.0290.01.0193.692731
1250.0320.01.0143.379958
1360.0320.01.0166.800806
1470.0320.01.0181.415162
1550.0350.01.0123.243688
1660.0350.01.0151.887299
1770.0350.01.0173.868535
1850.0290.01.1183.777624
1960.0290.01.1209.207028
2070.0290.01.1241.603651
2150.0320.01.1166.395578
2260.0320.01.1197.015377
2370.0320.01.1233.256967
2450.0350.01.1157.775296
2560.0350.01.1179.402414
2670.0350.01.1199.892743
\n", 513 | "
" 514 | ], 515 | "text/plain": [ 516 | " Pressure Temperature Flow rate result\n", 517 | "0 50.0 290.0 0.9 118.973619\n", 518 | "1 60.0 290.0 0.9 143.953286\n", 519 | "2 70.0 290.0 0.9 161.500565\n", 520 | "3 50.0 320.0 0.9 110.895524\n", 521 | "4 60.0 320.0 0.9 134.219954\n", 522 | "5 70.0 320.0 0.9 151.631696\n", 523 | "6 50.0 350.0 0.9 102.399310\n", 524 | "7 60.0 350.0 0.9 118.265809\n", 525 | "8 70.0 350.0 0.9 139.410702\n", 526 | "9 50.0 290.0 1.0 153.581631\n", 527 | "10 60.0 290.0 1.0 171.824979\n", 528 | "11 70.0 290.0 1.0 193.692731\n", 529 | "12 50.0 320.0 1.0 143.379958\n", 530 | "13 60.0 320.0 1.0 166.800806\n", 531 | "14 70.0 320.0 1.0 181.415162\n", 532 | "15 50.0 350.0 1.0 123.243688\n", 533 | "16 60.0 350.0 1.0 151.887299\n", 534 | "17 70.0 350.0 1.0 173.868535\n", 535 | "18 50.0 290.0 1.1 183.777624\n", 536 | "19 60.0 290.0 1.1 209.207028\n", 537 | "20 70.0 290.0 1.1 241.603651\n", 538 | "21 50.0 320.0 1.1 166.395578\n", 539 | "22 60.0 320.0 1.1 197.015377\n", 540 | "23 70.0 320.0 1.1 233.256967\n", 541 | "24 50.0 350.0 1.1 157.775296\n", 542 | "25 60.0 350.0 1.1 179.402414\n", 543 | "26 70.0 350.0 1.1 199.892743" 544 | ] 545 | }, 546 | "execution_count": 89, 547 | "metadata": {}, 548 | "output_type": "execute_result" 549 | } 550 | ], 551 | "source": [ 552 | "df['result']=df.apply(gen_result,axis=1)\n", 553 | "df" 554 | ] 555 | }, 556 | { 557 | "cell_type": "code", 558 | "execution_count": 96, 559 | "metadata": {}, 560 | "outputs": [], 561 | "source": [ 562 | "def plot_result(num_runs=5,randomness=0.05,variable=None):\n", 563 | " \"\"\"\n", 564 | " Plots the result of the experiment for a num of runs\n", 565 | " \"\"\"\n", 566 | " \n", 567 | " plots=[]\n", 568 | " df=build_full_fact({'Pressure':[50,60,70],'Temperature':[290, 320, 350],'Flow rate':[0.9,1.0,1.1]})\n", 569 | " \n", 570 | " if variable==None:\n", 571 | " var=np.random.choice(df.columns)\n", 572 | " else:\n", 573 | " var=variable\n", 574 | " \n", 575 | " for i in range(num_runs):\n", 576 | " df['result']=df.apply(gen_result,randomness=randomness,axis=1)\n", 577 | " sns.boxplot(x=var,y='result',data=df)\n", 578 | " plt.show()" 579 | ] 580 | }, 581 | { 582 | "cell_type": "code", 583 | "execution_count": 99, 584 | "metadata": {}, 585 | "outputs": [ 586 | { 587 | "data": { 588 | "image/png": "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\n", 589 | "text/plain": [ 590 | "
" 591 | ] 592 | }, 593 | "metadata": {}, 594 | "output_type": "display_data" 595 | }, 596 | { 597 | "data": { 598 | "image/png": "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\n", 599 | "text/plain": [ 600 | "
" 601 | ] 602 | }, 603 | "metadata": {}, 604 | "output_type": "display_data" 605 | }, 606 | { 607 | "data": { 608 | "image/png": "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\n", 609 | "text/plain": [ 610 | "
" 611 | ] 612 | }, 613 | "metadata": {}, 614 | "output_type": "display_data" 615 | }, 616 | { 617 | "data": { 618 | "image/png": "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\n", 619 | "text/plain": [ 620 | "
" 621 | ] 622 | }, 623 | "metadata": {}, 624 | "output_type": "display_data" 625 | }, 626 | { 627 | "data": { 628 | "image/png": "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\n", 629 | "text/plain": [ 630 | "
" 631 | ] 632 | }, 633 | "metadata": {}, 634 | "output_type": "display_data" 635 | } 636 | ], 637 | "source": [ 638 | "plot_result(variable='Pressure')" 639 | ] 640 | }, 641 | { 642 | "cell_type": "code", 643 | "execution_count": 101, 644 | "metadata": {}, 645 | "outputs": [ 646 | { 647 | "data": { 648 | "image/png": "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\n", 649 | "text/plain": [ 650 | "
" 651 | ] 652 | }, 653 | "metadata": {}, 654 | "output_type": "display_data" 655 | } 656 | ], 657 | "source": [ 658 | "sns.regplot(x='Pressure',y='result',data=df)\n", 659 | "plt.show()" 660 | ] 661 | }, 662 | { 663 | "cell_type": "code", 664 | "execution_count": null, 665 | "metadata": {}, 666 | "outputs": [], 667 | "source": [] 668 | } 669 | ], 670 | "metadata": { 671 | "kernelspec": { 672 | "display_name": "Python 3", 673 | "language": "python", 674 | "name": "python3" 675 | }, 676 | "language_info": { 677 | "codemirror_mode": { 678 | "name": "ipython", 679 | "version": 3 680 | }, 681 | "file_extension": ".py", 682 | "mimetype": "text/x-python", 683 | "name": "python", 684 | "nbconvert_exporter": "python", 685 | "pygments_lexer": "ipython3", 686 | "version": "3.6.4" 687 | } 688 | }, 689 | "nbformat": 4, 690 | "nbformat_minor": 2 691 | } 692 | -------------------------------------------------------------------------------- /Notebooks/Readme.md: -------------------------------------------------------------------------------- 1 | ## Practice Notebook 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Design-of-experiment (DOE) matrix generator for engineering and statistics 2 |

3 | 4 | ### Copyright Notice and Code repository 5 | ***Copyright (c): 2018-2028, Dr. Tirthajyoti Sarkar, Sunnyvale, CA 94086*** 6 | 7 | It uses a MIT License, so although I retain the copyright of this particular code, please feel free to exercise your rights of the free software by using and enhancing it. 8 | 9 | Please get the codebase from [here](https://github.com/tirthajyoti/Design-of-experiment-Python). 10 | 11 | ## UPDATE (July 2019) 12 | 13 | A formal PyPi-hosted package `doepy` has been released (Alpha 0.0.1 version). [Here is the Github repo](https://github.com/tirthajyoti/doepy). 14 | 15 | You can easy install the package by **`pip install doepy`** and use that for generating CSV files of design tables. 16 | 17 | All the design schemes, mentioned in this repo, are supported in this package now. 18 | 19 | [READ the Docs here](https://doepy.readthedocs.io/en/latest/). 20 | 21 | ## Table of Contents 22 | 1. [Introduction](#Introduction) 23 | * [What is a scientific experiment?](#ScienceExp) 24 | * [What is Experimental Design?](#ExpDesign) 25 | * [Options for open-source DOE builder package in Python?](#PythonOpenSource) 26 | 2. [Features](#Features) 27 | * [Limitation of the foundation packages used](#Limitations) 28 | * [Simplified user interface](#SimpleUI) 29 | * [Designs available](#DesignOptions) 30 | 3. [How to use it?](#HowTo) 31 | * [What supporitng packages are required?](#SupportingPackages) 32 | * [Eratta for using PyDOE](#Eratta) 33 | * [How to run the program?](#HowToRun) 34 | * [Is an installer/Python library available?](#Installer) 35 | 4. [Examples](#Examples) 36 | * [Full-factorial design](#FullFactorial) 37 | * [Fractional-factorial design](#FracFactorial) 38 | * [Central-composite design](#CentralComposite) 39 | * [Latin Hypercube design](#LatinHypercube) 40 | 5. [Acknowledgements and Requirements](#Acknowledgement) 41 | 42 | ## Introduction 43 | [Design of Experiment (DOE)](https://en.wikipedia.org/wiki/Design_of_experiments) is an important activity for any scientist, engineer, or statistician planning to conduct experimental analysis. This exercise has become **critical in this age of rapidly expanding field of data science and associated statistical modeling and machine learning**. A well-planned DOE can give a researcher meaningful data set to act upon with optimal number of experiments preserving critical resources. 44 | 45 | > After all, aim of Data Science is essentially to conduct highest quality scientific investigation and modeling with real world data. And to do good science with data, one needs to collect it through carefully thought-out experiment to cover all corner cases and reduce any possible bias. 46 | 47 | ### What is a scientific experiment? 48 | In its simplest form, a scientific experiment aims at predicting the outcome by introducing a change of the preconditions, which is represented by one or more [independent variables](https://en.wikipedia.org/wiki/Dependent_and_independent_variables), also referred to as “input variables” or “predictor variables.” The change in one or more independent variables is generally hypothesized to result in a change in one or more [dependent variables](https://en.wikipedia.org/wiki/Dependent_and_independent_variables), also referred to as “output variables” or “response variables.” The experimental design may also identify [control variables](https://en.wikipedia.org/wiki/Controlling_for_a_variable) that must be held constant to prevent external factors from affecting the results. 49 | 50 | ### What is Experimental Design? 51 | Experimental design involves not only the selection of suitable independent, dependent, and control variables, but planning the delivery of the experiment under statistically optimal conditions given the constraints of available resources. There are multiple approaches for determining the set of design points (unique combinations of the settings of the independent variables) to be used in the experiment. 52 | 53 | Main concerns in experimental design include the establishment of [validity](https://en.wikipedia.org/wiki/Validity_%28statistics%29), [reliability](https://en.wikipedia.org/wiki/Reliability_%28statistics%29), and [replicability](https://en.wikipedia.org/wiki/Reproducibility). For example, these concerns can be partially addressed by carefully choosing the independent variable, reducing the risk of measurement error, and ensuring that the documentation of the method is sufficiently detailed. Related concerns include achieving appropriate levels of [statistical power](https://en.wikipedia.org/wiki/Statistical_power) and [sensitivity](https://en.wikipedia.org/wiki/Sensitivity_and_specificity). 54 | 55 | Need for careful design of experiment arises in all fields of serious scientific, technological, and even social science investigation — *computer science, physics, geology, political science, electrical engineering, psychology, business marketing analysis, financial analytics*, etc… 56 | 57 | ### Options for open-source DOE builder package in Python? 58 | Unfortunately, majority of the state-of-the-art DOE generators are part of commercial statistical software packages like [JMP (SAS)](https://www.jmp.com/) or [Minitab](www.minitab.com/en-US/default.aspx). However, a researcher will surely be benefited if there exists an open-source code which presents an intuitive user interface for generating an experimental design plan from a simple list of input variables. There are a couple of DOE builder Python packages but individually they don’t cover all the necessary DOE methods and they lack a simplified user API, where one can just input a CSV file of input variables’ range and get back the DOE matrix in another CSV file. 59 | 60 | ## Features 61 | This set of codes is a collection of functions which wrap around the core packages (mentioned below) and generate **design-of-experiment (DOE) matrices** for a statistician or engineer from an arbitrary range of input variables. 62 | 63 | ### Limitation of the foundation packages used 64 | Both the core packages, which act as foundations to this repo, are not complete in the sense that they do not cover all the necessary functions to generate DOE table that a design engineer may need while planning an experiment. Also, they offer only low-level APIs in the sense that the standard output from them are normalized numpy arrays. It was felt that users, who may not be comfortable in dealing with Python objects directly, should be able to take advantage of their functionalities through a simplified user interface. 65 | 66 | ### Simplified user interface 67 | ***User just needs to provide a simple CSV file with a single table of variables and their ranges (2-level i.e. min/max or 3-level).*** Some of the functions work with 2-level min/max range while some others need 3-level ranges from the user (low-mid-high). Intelligence is built into the code to handle the case if the range input is not appropriate and to generate levels by simple linear interpolation from the given input. The code will generate the DOE as per user's choice and write the matrix in a CSV file on to the disk. In this way, ***the only API user is exposed to are input and output CSV files. These files then can be used in any engineering simulator, software, process-control module, or fed into process equipments.*** 68 | 69 | ### Designs available 70 | * Full factorial, 71 | * 2-level fractional factorial, 72 | * Plackett-Burman, 73 | * Sukharev grid, 74 | * Box-Behnken, 75 | * Box-Wilson (Central-composite) with center-faced option, 76 | * Box-Wilson (Central-composite) with center-inscribed option, 77 | * Box-Wilson (Central-composite) with center-circumscribed option, 78 | * Latin hypercube (simple), 79 | * Latin hypercube (space-filling), 80 | * Random k-means cluster, 81 | * Maximin reconstruction, 82 | * Halton sequence based, 83 | * Uniform random matrix 84 | 85 | ## How to use it? 86 | ### What supporitng packages are required? 87 | First make sure you have all the necessary packages installed. You can simply run the .bash (Unix/Linux) and .bat (Windows) files provided in the repo, to install those packages from your command line interface. They contain the following commands, 88 | 89 | ``` 90 | pip install numpy 91 | pip install pandas 92 | pip install matplotlib 93 | pip install pydoe 94 | pip install diversipy 95 | ``` 96 | ### Eratta for using PyDOE 97 | Please note that as installed, PyDOE will throw some error related to type conversion. There are two options 98 | * I have modified the pyDOE code suitably and included a file with re-written functions in the repo. This is the file called by the program while executing, so you should see no error. 99 | * If you encounter any error, you could try to modify the PyDOE code by going to the folder where pyDOE files are copied and copying the two files `doe_factorial.py` and `doe_box_behnken.py` supplied with this repo. 100 | 101 | ### How to run the program? 102 | Note this is just a code repository and not a installer package. For the time being, please clone [this repo from GitHub](https://github.com/tirthajyoti/Design-of-experiment-Python), store all the files in a local directory. 103 | 104 | **``git clone https://github.com/tirthajyoti/Design-of-experiment-Python.git``** 105 | 106 | Then start using the software by simply typing, 107 | 108 | **``python Main.py``** 109 | 110 | After this, a simple menu will be printed on the screen and you will be prompted for a choice of number (a DOE) and name of the input CSV file (containing the names and ranges of your variables). 111 | 112 |

113 | 114 | **You must have an input parameters CSV file stored in the same directory** that you are running this code from. You should use the supplied generic CSV file as an example. Please put the factors in the columns and the levels in the row (not the other way around). Couple of example CSV files are provided in the repo. Feel free to modify them as per your needs. 115 | 116 | ### Is an installer/Python library available? 117 | At this time, **No**. I plan to work on turning this into a full-fledged Python library which can be installed from PyPi repository by a PIP command. But I cannot promise any timeline for that :-) ***If somebody wants to collaborate and work on an installer, please feel free to do so.*** 118 | 119 | ## Examples 120 | Let's say the input file contains the following table for the parameters range. Imagine this as a generic example of a checmical process in a plant. 121 | 122 | Pressure | Temperature | FlowRate | Time 123 | ------------ | ------------- | -------------|----------------- 124 | 40 | 290 | 0.2 | 5 125 | 55 | 320 | 0.3 | 8 126 | 70 | 350 | 0.4 | 11 127 | 128 | ### Full-factorial design 129 |

130 | 131 |

132 | If we build a full-factorial DOE out of this, we will get a table with 81 entries because 4 factors permuted in 3 levels result in 3^4=81 combinations! 133 | 134 | Pressure | Temperature | FlowRate | Time 135 | ------------ | ------------- | -------------|----------------- 136 | 40 | 290 | 0.2 | 5 137 | 50 | 290 | 0.2 | 5 138 | 70 | 290 | 0.2 | 5 139 | 40 | 320 | 0.2 | 5 140 | 50 | 320 | 0.2 | 5 141 | 70 | 320 | 0.2 | 5 142 | ... | ... | ... | ... 143 | ...| ... | ... | ... 144 | 40 | 290 | 0.3 | 8 145 | 50 | 290 | 0.3 | 8 146 | 70 | 290 | 0.3 | 8 147 | 40 | 320 | 0.3 | 8 148 | 50 | 320 | 0.3 | 8 149 | 70 | 320 | 0.3 | 8 150 | ... | ... | ... | ... 151 | ...| ... | ... | ... 152 | 40 | 320 | 0.4 | 11 153 | 50 | 320 | 0.4 | 11 154 | 70 | 320 | 0.4 | 11 155 | 40 | 350 | 0.4 | 11 156 | 50 | 350 | 0.4 | 11 157 | 70 | 350 | 0.4 | 11 158 | 159 | ### Fractional-factorial design 160 | Clearly the full-factorial designs grows quickly! Engineers and scientists therefore often use half-factorial/fractional-factorial designs where they confound one or more factors with other factors and build a reduced DOE. Let's say we decide to build a 2-level fractional factorial of this set of variables with the 4th variables as the confounding factor (i.e. not an independent variable but as a function of other variables). If the functional relationship is "A B C BC" i.e. the 4th parameter vary depending only on 2nd and 3rd parameter, the output table could look like, 161 | 162 | Pressure | Temperature | FlowRate | Time 163 | ------------ | ------------- | -------------|----------------- 164 | 40 | 290 | 0.2 | 11 165 | 70 | 290 | 0.2 | 11 166 | 40 | 350 | 0.2 | 5 167 | 70 | 350 | 0.2 | 5 168 | 40 | 290 | 0.4 | 5 169 | 70 | 290 | 0.4 | 5 170 | 40 | 350 | 0.4 | 11 171 | 70 | 350 | 0.4 | 11 172 | 173 | ### Central-composite design 174 |

175 | A Box-Wilson Central Composite Design, commonly called 'a central composite design,' contains an imbedded factorial or fractional factorial design with center points that is augmented with a group of 'star points' that allow estimation of curvature. One central composite design consists of cube points at the corners of a unit cube that is the product of the intervals [-1,1], star points along the axes at or outside the cube, and center points at the origin. Central composite designs are of three types. Circumscribed (CCC) designs are as described above. Inscribed (CCI) designs are as described above, but scaled so the star points take the values -1 and +1, and the cube points lie in the interior of the cube. Faced (CCF) designs have the star points on the faces of the cube. Faced designs have three levels per factor, in contrast with the other types that have five levels per factor. The following figure shows these three types of designs for three factors. [Read this page] (http://blog.minitab.com/blog/understanding-statistics/getting-started-with-factorial-design-of-experiments-doe) for more information about this kind of design philosophy. 176 | 177 | ### Latin Hypercube design 178 |

179 | 180 | Sometimes, a set of ***randomized design points within a given range*** could be attractive for the experimenter to asses the impact of the process variables on the output. [Monte Carlo simulations](https://en.wikipedia.org/wiki/Monte_Carlo_method) are close example of this approach. However, a Latin Hypercube design is better choice for experimental design rather than building a complete random matrix as it tries to subdivide the sample space in smaller cells and choose only one element out of each subcell. This way, a more ***'uniform spreading' of the random sample points*** can be obtained. User can choose the density of sample points. For example, if we choose to generate a Latin Hypercube of 12 experiments from the same input files, that could look like, 181 | 182 | Pressure | Temperature | FlowRate | Time 183 | ------------ | ------------- | -------------|----------------- 184 | 63.16 | 313.32 | 0.37 | 10.52 185 | 61.16 | 343.88 | 0.23 | 5.04 186 | 57.83 | 327.46 | 0.35 | 9.47 187 | 68.61 | 309.81 | 0.35 | 8.39 188 | 66.01 | 301.29 | 0.22 | 6.34 189 | 45.76 | 347.97 | 0.27 | 6.94 190 | 40.48 | 320.72 | 0.29 | 9.68 191 | 51.46 | 293.35 | 0.20 | 7.11 192 | 43.63 | 334.92 | 0.30 | 7.66 193 | 47.87 | 339.68 | 0.26 | 8.59 194 | 55.28 | 317.68 | 0.39 | 5.61 195 | 53.99 | 297.07 | 0.32 | 10.43 196 | 197 | Of course, there is no guarantee that you will get the same matrix if you run this function because this are randomly sampled, but you get the idea! 198 | 199 | ## Acknowledgements and Requirements 200 | The code was written in Python 3.6. It uses following external packages that needs to be installed on your system to use it, 201 | * pydoe: A package designed to help the scientist, engineer, statistician, etc., to construct appropriate experimental designs. [Check the docs here](https://pythonhosted.org/pyDOE/). 202 | * diversipy: A collection of algorithms for sampling in hypercubes, selecting diverse subsets, and measuring diversity. [Check the docs here](https://www.simonwessing.de/diversipy/doc/). 203 | * numpy 204 | * pandas 205 | -------------------------------------------------------------------------------- /Read_Write_CSV.py: -------------------------------------------------------------------------------- 1 | import csv 2 | 3 | # ========================================================== 4 | # Function for reading a CSV file into a dictionary format 5 | # ========================================================== 6 | 7 | def read_variables_csv(csvfile): 8 | """ 9 | Builds a Python dictionary object from an input CSV file. 10 | Helper function to read a CSV file on the disk, where user stores the limits/ranges of the process variables. 11 | """ 12 | dict_key={} 13 | try: 14 | with open(csvfile) as f: 15 | reader = csv.DictReader(f) 16 | fields = reader.fieldnames 17 | for field in fields: 18 | lst=[] 19 | with open(csvfile) as f: 20 | reader = csv.DictReader(f) 21 | for row in reader: 22 | lst.append(float(row[field])) 23 | dict_key[field]=lst 24 | 25 | return dict_key 26 | except: 27 | print("Error in reading the specified file from the disk. Please make sure it is in current directory.") 28 | return -1 29 | 30 | # =============================================================== 31 | # Function for writing the design matrix into an output CSV file 32 | # =============================================================== 33 | 34 | def write_csv(df,filename): 35 | """ 36 | Writes a CSV file on to the disk from the internal Pandas DataFrame object i.e. the computed design matrix 37 | """ 38 | try: 39 | filename=filename+'.csv' 40 | df.to_csv(filename) 41 | except: 42 | return -1 -------------------------------------------------------------------------------- /SC-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tirthajyoti/Design-of-experiment-Python/fc1d00b9525e7e583153727a8979b9427122a3e4/SC-1.PNG -------------------------------------------------------------------------------- /Setup.bash: -------------------------------------------------------------------------------- 1 | pip install numpy 2 | pip install pandas 3 | pip install matplotlib 4 | pip install pydoe 5 | pip install diversipy -------------------------------------------------------------------------------- /Setup.bat: -------------------------------------------------------------------------------- 1 | pip install numpy 2 | pip install pandas 3 | pip install matplotlib 4 | pip install pydoe 5 | pip install diversipy -------------------------------------------------------------------------------- /User_input.py: -------------------------------------------------------------------------------- 1 | # =========================================================================================== 2 | # Function for accepting the user input (choice) for desired DOE and the input CSV file name 3 | # =========================================================================================== 4 | 5 | def user_input(): 6 | print("-"*60+"\n"+"Design of Experiments menu\n"+"-"*60+"\n") 7 | list_doe = ["1) Full factorial", 8 | "2) 2-level fractional factorial", 9 | "3) Plackett-Burman", 10 | "4) Sukharev grid", 11 | "5) Box-Behnken", 12 | "6) Box-Wilson (Central-composite) with center-faced option", 13 | "7) Box-Wilson (Central-composite) with center-inscribed option", 14 | "8) Box-Wilson (Central-composite) with center-circumscribed option", 15 | "9) Latin hypercube (simple)", 16 | "10) Latin hypercube (space-filling)", 17 | "11) Random k-means cluster", 18 | "12) Maximin reconstruction", 19 | "13) Halton sequence based", 20 | "14) Uniform random matrix" 21 | ] 22 | 23 | for choice in list_doe: 24 | print(choice) 25 | print("-"*60) 26 | 27 | doe_choice = int(input("Please make a choice for your Deisgn-of-Experiment build: ")) 28 | infile = str(input("Please enter the name of the input csv file (enter only the name without the CSV extension): ")) 29 | print() 30 | 31 | if (infile[-3:]!='csv'): 32 | infile=infile+'.csv' 33 | 34 | return (doe_choice,infile) -------------------------------------------------------------------------------- /doe_box_behnken.py: -------------------------------------------------------------------------------- 1 | """ 2 | This code was originally published by the following individuals for use with 3 | Scilab: 4 | Copyright (C) 2012 - 2013 - Michael Baudin 5 | Copyright (C) 2012 - Maria Christopoulou 6 | Copyright (C) 2010 - 2011 - INRIA - Michael Baudin 7 | Copyright (C) 2009 - Yann Collette 8 | Copyright (C) 2009 - CEA - Jean-Marc Martinez 9 | 10 | website: forge.scilab.org/index.php/p/scidoe/sourcetree/master/macros 11 | 12 | Much thanks goes to these individuals. It has been converted to Python by 13 | Abraham Lee. 14 | """ 15 | 16 | import numpy as np 17 | from pyDOE.doe_factorial import ff2n 18 | from pyDOE.doe_repeat_center import repeat_center 19 | 20 | __all__ = ['bbdesign'] 21 | 22 | def bbdesign(n, center=None): 23 | """ 24 | Create a Box-Behnken design 25 | 26 | Parameters 27 | ---------- 28 | n : int 29 | The number of factors in the design 30 | 31 | Optional 32 | -------- 33 | center : int 34 | The number of center points to include (default = 1). 35 | 36 | Returns 37 | ------- 38 | mat : 2d-array 39 | The design matrix 40 | 41 | Example 42 | ------- 43 | :: 44 | 45 | >>> bbdesign(3) 46 | array([[-1., -1., 0.], 47 | [ 1., -1., 0.], 48 | [-1., 1., 0.], 49 | [ 1., 1., 0.], 50 | [-1., 0., -1.], 51 | [ 1., 0., -1.], 52 | [-1., 0., 1.], 53 | [ 1., 0., 1.], 54 | [ 0., -1., -1.], 55 | [ 0., 1., -1.], 56 | [ 0., -1., 1.], 57 | [ 0., 1., 1.], 58 | [ 0., 0., 0.], 59 | [ 0., 0., 0.], 60 | [ 0., 0., 0.]]) 61 | 62 | """ 63 | assert n>=3, 'Number of variables must be at least 3' 64 | 65 | # First, compute a factorial DOE with 2 parameters 66 | H_fact = ff2n(2) 67 | # Now we populate the real DOE with this DOE 68 | 69 | # We made a factorial design on each pair of dimensions 70 | # - So, we created a factorial design with two factors 71 | # - Make two loops 72 | Index = 0 73 | nb_lines = int((0.5*n*(n-1))*H_fact.shape[0]) 74 | H = repeat_center(n, nb_lines) 75 | 76 | for i in range(n - 1): 77 | for j in range(i + 1, n): 78 | Index = Index + 1 79 | H[max([0, (Index - 1)*H_fact.shape[0]]):Index*H_fact.shape[0], i] = H_fact[:, 0] 80 | H[max([0, (Index - 1)*H_fact.shape[0]]):Index*H_fact.shape[0], j] = H_fact[:, 1] 81 | 82 | if center is None: 83 | if n<=16: 84 | points= [0, 0, 0, 3, 3, 6, 6, 6, 8, 9, 10, 12, 12, 13, 14, 15, 16] 85 | center = points[n] 86 | else: 87 | center = n 88 | 89 | H = np.c_[H.T, repeat_center(n, center).T].T 90 | 91 | return H 92 | -------------------------------------------------------------------------------- /doe_factorial.py: -------------------------------------------------------------------------------- 1 | """ 2 | This code was originally published by the following individuals for use with 3 | Scilab: 4 | Copyright (C) 2012 - 2013 - Michael Baudin 5 | Copyright (C) 2012 - Maria Christopoulou 6 | Copyright (C) 2010 - 2011 - INRIA - Michael Baudin 7 | Copyright (C) 2009 - Yann Collette 8 | Copyright (C) 2009 - CEA - Jean-Marc Martinez 9 | 10 | website: forge.scilab.org/index.php/p/scidoe/sourcetree/master/macros 11 | 12 | Much thanks goes to these individuals. It has been converted to Python by 13 | Abraham Lee. 14 | """ 15 | 16 | import re 17 | import numpy as np 18 | 19 | __all__ = ['np', 'fullfact', 'ff2n', 'fracfact'] 20 | 21 | def fullfact(levels): 22 | """ 23 | Create a general full-factorial design 24 | 25 | Parameters 26 | ---------- 27 | levels : array-like 28 | An array of integers that indicate the number of levels of each input 29 | design factor. 30 | 31 | Returns 32 | ------- 33 | mat : 2d-array 34 | The design matrix with coded levels 0 to k-1 for a k-level factor 35 | 36 | Example 37 | ------- 38 | :: 39 | 40 | >>> fullfact([2, 4, 3]) 41 | array([[ 0., 0., 0.], 42 | [ 1., 0., 0.], 43 | [ 0., 1., 0.], 44 | [ 1., 1., 0.], 45 | [ 0., 2., 0.], 46 | [ 1., 2., 0.], 47 | [ 0., 3., 0.], 48 | [ 1., 3., 0.], 49 | [ 0., 0., 1.], 50 | [ 1., 0., 1.], 51 | [ 0., 1., 1.], 52 | [ 1., 1., 1.], 53 | [ 0., 2., 1.], 54 | [ 1., 2., 1.], 55 | [ 0., 3., 1.], 56 | [ 1., 3., 1.], 57 | [ 0., 0., 2.], 58 | [ 1., 0., 2.], 59 | [ 0., 1., 2.], 60 | [ 1., 1., 2.], 61 | [ 0., 2., 2.], 62 | [ 1., 2., 2.], 63 | [ 0., 3., 2.], 64 | [ 1., 3., 2.]]) 65 | 66 | """ 67 | n = len(levels) # number of factors 68 | nb_lines = np.prod(levels) # number of trial conditions 69 | H = np.zeros((nb_lines, n)) 70 | 71 | level_repeat = 1 72 | range_repeat = np.prod(levels) 73 | for i in range(n): 74 | range_repeat //= levels[i] 75 | lvl = [] 76 | for j in range(levels[i]): 77 | lvl += [j]*level_repeat 78 | rng = lvl*range_repeat 79 | level_repeat *= levels[i] 80 | H[:, i] = rng 81 | 82 | return H 83 | 84 | ################################################################################ 85 | 86 | def ff2n(n): 87 | """ 88 | Create a 2-Level full-factorial design 89 | 90 | Parameters 91 | ---------- 92 | n : int 93 | The number of factors in the design. 94 | 95 | Returns 96 | ------- 97 | mat : 2d-array 98 | The design matrix with coded levels -1 and 1 99 | 100 | Example 101 | ------- 102 | :: 103 | 104 | >>> ff2n(3) 105 | array([[-1., -1., -1.], 106 | [ 1., -1., -1.], 107 | [-1., 1., -1.], 108 | [ 1., 1., -1.], 109 | [-1., -1., 1.], 110 | [ 1., -1., 1.], 111 | [-1., 1., 1.], 112 | [ 1., 1., 1.]]) 113 | 114 | """ 115 | return 2*fullfact([2]*n) - 1 116 | 117 | ################################################################################ 118 | 119 | def fracfact(gen): 120 | """ 121 | Create a 2-level fractional-factorial design with a generator string. 122 | 123 | Parameters 124 | ---------- 125 | gen : str 126 | A string, consisting of lowercase, uppercase letters or operators "-" 127 | and "+", indicating the factors of the experiment 128 | 129 | Returns 130 | ------- 131 | H : 2d-array 132 | A m-by-n matrix, the fractional factorial design. m is 2^k, where k 133 | is the number of letters in ``gen``, and n is the total number of 134 | entries in ``gen``. 135 | 136 | Notes 137 | ----- 138 | In ``gen`` we define the main factors of the experiment and the factors 139 | whose levels are the products of the main factors. For example, if 140 | 141 | gen = "a b ab" 142 | 143 | then "a" and "b" are the main factors, while the 3rd factor is the product 144 | of the first two. If we input uppercase letters in ``gen``, we get the same 145 | result. We can also use the operators "+" and "-" in ``gen``. 146 | 147 | For example, if 148 | 149 | gen = "a b -ab" 150 | 151 | then the 3rd factor is the opposite of the product of "a" and "b". 152 | 153 | The output matrix includes the two level full factorial design, built by 154 | the main factors of ``gen``, and the products of the main factors. The 155 | columns of ``H`` follow the sequence of ``gen``. 156 | 157 | For example, if 158 | 159 | gen = "a b ab c" 160 | 161 | then columns H[:, 0], H[:, 1], and H[:, 3] include the two level full 162 | factorial design and H[:, 2] includes the products of the main factors. 163 | 164 | Examples 165 | -------- 166 | :: 167 | 168 | >>> fracfact("a b ab") 169 | array([[-1., -1., 1.], 170 | [ 1., -1., -1.], 171 | [-1., 1., -1.], 172 | [ 1., 1., 1.]]) 173 | 174 | >>> fracfact("A B AB") 175 | array([[-1., -1., 1.], 176 | [ 1., -1., -1.], 177 | [-1., 1., -1.], 178 | [ 1., 1., 1.]]) 179 | 180 | >>> fracfact("a b -ab c +abc") 181 | array([[-1., -1., -1., -1., -1.], 182 | [ 1., -1., 1., -1., 1.], 183 | [-1., 1., 1., -1., 1.], 184 | [ 1., 1., -1., -1., -1.], 185 | [-1., -1., -1., 1., 1.], 186 | [ 1., -1., 1., 1., -1.], 187 | [-1., 1., 1., 1., -1.], 188 | [ 1., 1., -1., 1., 1.]]) 189 | 190 | """ 191 | # Recognize letters and combinations 192 | A = [item for item in re.split('\-?\s?\+?', gen) if item] # remove empty strings 193 | C = [len(item) for item in A] 194 | 195 | # Indices of single letters (main factors) 196 | I = [i for i, item in enumerate(C) if item==1] 197 | 198 | # Indices of letter combinations (we need them to fill out H2 properly). 199 | J = [i for i, item in enumerate(C) if item!=1] 200 | 201 | # Check if there are "-" or "+" operators in gen 202 | U = [item for item in gen.split(' ') if item] # remove empty strings 203 | 204 | # If R1 is either None or not, the result is not changed, since it is a 205 | # multiplication of 1. 206 | R1 = _grep(U, '+') 207 | R2 = _grep(U, '-') 208 | 209 | # Fill in design with two level factorial design 210 | H1 = ff2n(len(I)) 211 | H = np.zeros((H1.shape[0], len(C))) 212 | H[:, I] = H1 213 | 214 | # Recognize combinations and fill in the rest of matrix H2 with the proper 215 | # products 216 | for k in J: 217 | # For lowercase letters 218 | xx = np.array([ord(c) for c in A[k]]) - 97 219 | 220 | # For uppercase letters 221 | if np.any(xx<0): 222 | xx = np.array([ord(c) for c in A[k]]) - 65 223 | 224 | H[:, k] = np.prod(H1[:, xx], axis=1) 225 | 226 | # Update design if gen includes "-" operator 227 | if R2: 228 | H[:, R2] *= -1 229 | 230 | # Return the fractional factorial design 231 | return H 232 | 233 | def _grep(haystack, needle): 234 | try: 235 | haystack[0] 236 | except (TypeError, AttributeError): 237 | return [0] if needle in haystack else [] 238 | else: 239 | locs = [] 240 | for idx, item in enumerate(haystack): 241 | if needle in item: 242 | locs += [idx] 243 | return locs 244 | -------------------------------------------------------------------------------- /params.csv: -------------------------------------------------------------------------------- 1 | Pressure,Temperature,FlowRate,Time 2 | 40,290,0.2,5 3 | 55,320,0.3,8 4 | 70,350,0.4,11 5 | -------------------------------------------------------------------------------- /pyDOE_corrected.py: -------------------------------------------------------------------------------- 1 | """ 2 | This code was originally published by the following individuals for use with 3 | Scilab: 4 | Copyright (C) 2012 - 2013 - Michael Baudin 5 | Copyright (C) 2012 - Maria Christopoulou 6 | Copyright (C) 2010 - 2011 - INRIA - Michael Baudin 7 | Copyright (C) 2009 - Yann Collette 8 | Copyright (C) 2009 - CEA - Jean-Marc Martinez 9 | 10 | website: forge.scilab.org/index.php/p/scidoe/sourcetree/master/macros 11 | 12 | Much thanks goes to these individuals. It has been converted to Python by 13 | Abraham Lee. 14 | """ 15 | 16 | import re 17 | import numpy as np 18 | 19 | #__all__ = ['np', 'fullfact_corrected', 'ff2n', 'fracfact'] 20 | 21 | def fullfact_corrected(levels): 22 | """ 23 | Create a general full-factorial design 24 | 25 | Parameters 26 | ---------- 27 | levels : array-like 28 | An array of integers that indicate the number of levels of each input 29 | design factor. 30 | 31 | Returns 32 | ------- 33 | mat : 2d-array 34 | The design matrix with coded levels 0 to k-1 for a k-level factor 35 | 36 | Example 37 | ------- 38 | :: 39 | 40 | >>> fullfact([2, 4, 3]) 41 | array([[ 0., 0., 0.], 42 | [ 1., 0., 0.], 43 | [ 0., 1., 0.], 44 | [ 1., 1., 0.], 45 | [ 0., 2., 0.], 46 | [ 1., 2., 0.], 47 | [ 0., 3., 0.], 48 | [ 1., 3., 0.], 49 | [ 0., 0., 1.], 50 | [ 1., 0., 1.], 51 | [ 0., 1., 1.], 52 | [ 1., 1., 1.], 53 | [ 0., 2., 1.], 54 | [ 1., 2., 1.], 55 | [ 0., 3., 1.], 56 | [ 1., 3., 1.], 57 | [ 0., 0., 2.], 58 | [ 1., 0., 2.], 59 | [ 0., 1., 2.], 60 | [ 1., 1., 2.], 61 | [ 0., 2., 2.], 62 | [ 1., 2., 2.], 63 | [ 0., 3., 2.], 64 | [ 1., 3., 2.]]) 65 | 66 | """ 67 | n = len(levels) # number of factors 68 | nb_lines = np.prod(levels) # number of trial conditions 69 | H = np.zeros((nb_lines, n)) 70 | 71 | level_repeat = 1 72 | range_repeat = np.prod(levels) 73 | for i in range(n): 74 | range_repeat //= levels[i] 75 | lvl = [] 76 | for j in range(levels[i]): 77 | lvl += [j]*level_repeat 78 | rng = lvl*range_repeat 79 | level_repeat *= levels[i] 80 | H[:, i] = rng 81 | 82 | return H 83 | 84 | ################################################################################ 85 | 86 | def ff2n(n): 87 | """ 88 | Create a 2-Level full-factorial design 89 | 90 | Parameters 91 | ---------- 92 | n : int 93 | The number of factors in the design. 94 | 95 | Returns 96 | ------- 97 | mat : 2d-array 98 | The design matrix with coded levels -1 and 1 99 | 100 | Example 101 | ------- 102 | :: 103 | 104 | >>> ff2n(3) 105 | array([[-1., -1., -1.], 106 | [ 1., -1., -1.], 107 | [-1., 1., -1.], 108 | [ 1., 1., -1.], 109 | [-1., -1., 1.], 110 | [ 1., -1., 1.], 111 | [-1., 1., 1.], 112 | [ 1., 1., 1.]]) 113 | 114 | """ 115 | return 2*fullfact_corrected([2]*n) - 1 116 | 117 | ################################################################################ 118 | 119 | def fracfact(gen): 120 | """ 121 | Create a 2-level fractional-factorial design with a generator string. 122 | 123 | Parameters 124 | ---------- 125 | gen : str 126 | A string, consisting of lowercase, uppercase letters or operators "-" 127 | and "+", indicating the factors of the experiment 128 | 129 | Returns 130 | ------- 131 | H : 2d-array 132 | A m-by-n matrix, the fractional factorial design. m is 2^k, where k 133 | is the number of letters in ``gen``, and n is the total number of 134 | entries in ``gen``. 135 | 136 | Notes 137 | ----- 138 | In ``gen`` we define the main factors of the experiment and the factors 139 | whose levels are the products of the main factors. For example, if 140 | 141 | gen = "a b ab" 142 | 143 | then "a" and "b" are the main factors, while the 3rd factor is the product 144 | of the first two. If we input uppercase letters in ``gen``, we get the same 145 | result. We can also use the operators "+" and "-" in ``gen``. 146 | 147 | For example, if 148 | 149 | gen = "a b -ab" 150 | 151 | then the 3rd factor is the opposite of the product of "a" and "b". 152 | 153 | The output matrix includes the two level full factorial design, built by 154 | the main factors of ``gen``, and the products of the main factors. The 155 | columns of ``H`` follow the sequence of ``gen``. 156 | 157 | For example, if 158 | 159 | gen = "a b ab c" 160 | 161 | then columns H[:, 0], H[:, 1], and H[:, 3] include the two level full 162 | factorial design and H[:, 2] includes the products of the main factors. 163 | 164 | Examples 165 | -------- 166 | :: 167 | 168 | >>> fracfact("a b ab") 169 | array([[-1., -1., 1.], 170 | [ 1., -1., -1.], 171 | [-1., 1., -1.], 172 | [ 1., 1., 1.]]) 173 | 174 | >>> fracfact("A B AB") 175 | array([[-1., -1., 1.], 176 | [ 1., -1., -1.], 177 | [-1., 1., -1.], 178 | [ 1., 1., 1.]]) 179 | 180 | >>> fracfact("a b -ab c +abc") 181 | array([[-1., -1., -1., -1., -1.], 182 | [ 1., -1., 1., -1., 1.], 183 | [-1., 1., 1., -1., 1.], 184 | [ 1., 1., -1., -1., -1.], 185 | [-1., -1., -1., 1., 1.], 186 | [ 1., -1., 1., 1., -1.], 187 | [-1., 1., 1., 1., -1.], 188 | [ 1., 1., -1., 1., 1.]]) 189 | 190 | """ 191 | # Recognize letters and combinations 192 | A = [item for item in re.split('\-?\s?\+?', gen) if item] # remove empty strings 193 | C = [len(item) for item in A] 194 | 195 | # Indices of single letters (main factors) 196 | I = [i for i, item in enumerate(C) if item==1] 197 | 198 | # Indices of letter combinations (we need them to fill out H2 properly). 199 | J = [i for i, item in enumerate(C) if item!=1] 200 | 201 | # Check if there are "-" or "+" operators in gen 202 | U = [item for item in gen.split(' ') if item] # remove empty strings 203 | 204 | # If R1 is either None or not, the result is not changed, since it is a 205 | # multiplication of 1. 206 | R1 = _grep(U, '+') 207 | R2 = _grep(U, '-') 208 | 209 | # Fill in design with two level factorial design 210 | H1 = ff2n(len(I)) 211 | H = np.zeros((H1.shape[0], len(C))) 212 | H[:, I] = H1 213 | 214 | # Recognize combinations and fill in the rest of matrix H2 with the proper 215 | # products 216 | for k in J: 217 | # For lowercase letters 218 | xx = np.array([ord(c) for c in A[k]]) - 97 219 | 220 | # For uppercase letters 221 | if np.any(xx<0): 222 | xx = np.array([ord(c) for c in A[k]]) - 65 223 | 224 | H[:, k] = np.prod(H1[:, xx], axis=1) 225 | 226 | # Update design if gen includes "-" operator 227 | if R2: 228 | H[:, R2] *= -1 229 | 230 | # Return the fractional factorial design 231 | return H 232 | 233 | def _grep(haystack, needle): 234 | try: 235 | haystack[0] 236 | except (TypeError, AttributeError): 237 | return [0] if needle in haystack else [] 238 | else: 239 | locs = [] 240 | for idx, item in enumerate(haystack): 241 | if needle in item: 242 | locs += [idx] 243 | return locs 244 | 245 | 246 | #__all__ = ['bbdesign_corrected'] 247 | 248 | def bbdesign_corrected(n, center=None): 249 | """ 250 | Create a Box-Behnken design 251 | 252 | Parameters 253 | ---------- 254 | n : int 255 | The number of factors in the design 256 | 257 | Optional 258 | -------- 259 | center : int 260 | The number of center points to include (default = 1). 261 | 262 | Returns 263 | ------- 264 | mat : 2d-array 265 | The design matrix 266 | 267 | Example 268 | ------- 269 | :: 270 | 271 | >>> bbdesign(3) 272 | array([[-1., -1., 0.], 273 | [ 1., -1., 0.], 274 | [-1., 1., 0.], 275 | [ 1., 1., 0.], 276 | [-1., 0., -1.], 277 | [ 1., 0., -1.], 278 | [-1., 0., 1.], 279 | [ 1., 0., 1.], 280 | [ 0., -1., -1.], 281 | [ 0., 1., -1.], 282 | [ 0., -1., 1.], 283 | [ 0., 1., 1.], 284 | [ 0., 0., 0.], 285 | [ 0., 0., 0.], 286 | [ 0., 0., 0.]]) 287 | 288 | """ 289 | assert n>=3, 'Number of variables must be at least 3' 290 | 291 | # First, compute a factorial DOE with 2 parameters 292 | H_fact = ff2n(2) 293 | # Now we populate the real DOE with this DOE 294 | 295 | # We made a factorial design on each pair of dimensions 296 | # - So, we created a factorial design with two factors 297 | # - Make two loops 298 | Index = 0 299 | nb_lines = int((0.5*n*(n-1))*H_fact.shape[0]) 300 | H = repeat_center(n, nb_lines) 301 | 302 | for i in range(n - 1): 303 | for j in range(i + 1, n): 304 | Index = Index + 1 305 | H[max([0, (Index - 1)*H_fact.shape[0]]):Index*H_fact.shape[0], i] = H_fact[:, 0] 306 | H[max([0, (Index - 1)*H_fact.shape[0]]):Index*H_fact.shape[0], j] = H_fact[:, 1] 307 | 308 | if center is None: 309 | if n<=16: 310 | points= [0, 0, 0, 3, 3, 6, 6, 6, 8, 9, 10, 12, 12, 13, 14, 15, 16] 311 | center = points[n] 312 | else: 313 | center = n 314 | 315 | H = np.c_[H.T, repeat_center(n, center).T].T 316 | 317 | return H 318 | 319 | def repeat_center(n, repeat): 320 | """ 321 | Create the center-point portion of a design matrix 322 | 323 | Parameters 324 | ---------- 325 | n : int 326 | The number of factors in the original design 327 | repeat : int 328 | The number of center points to repeat 329 | 330 | Returns 331 | ------- 332 | mat : 2d-array 333 | The center-point portion of a design matrix (elements all zero). 334 | 335 | Example 336 | ------- 337 | :: 338 | 339 | >>> repeat_center(3, 2) 340 | array([[ 0., 0., 0.], 341 | [ 0., 0., 0.]]) 342 | 343 | """ 344 | return np.zeros((repeat, n)) 345 | --------------------------------------------------------------------------------