Repository: imputr/imputr (language: python)

Issue:
Merge latest project setup into core structure code



Discussion / hints:

Fix patch:
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..d663fad
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,4 @@
+[report]
+exclude_lines =
+    @abstractmethod
+    @abc.abstractmethod
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b6e4761..93b5f32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -127,3 +127,9 @@ dmypy.json
 
 # Pyre type checker
 .pyre/
+
+# VS Code
+.vscode/
+
+# Mac
+**/.DS_Store
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..ad63fcb
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,38 @@
+repos:
+  - repo: https://github.com/pre-commit/pre-commit-hooks
+    rev: v4.3.0
+    hooks:
+      - id: check-ast
+      - id: check-builtin-literals
+      - id: check-docstring-first
+      - id: check-merge-conflict
+      - id: check-yaml
+      - id: debug-statements
+      - id: end-of-file-fixer
+      - id: trailing-whitespace
+  - repo: https://github.com/asottile/pyupgrade
+    rev: v2.37.1
+    hooks:
+      - id: pyupgrade
+        args: [ "--py37-plus" ]
+  - repo: https://github.com/PyCQA/isort
+    rev: 5.10.1
+    hooks:
+      - id: isort
+  - repo: https://github.com/psf/black
+    rev: 22.6.0
+    hooks:
+      - id: black
+        args: [ --safe ]
+  - repo: https://github.com/PyCQA/flake8
+    rev: 4.0.1
+    hooks:
+      - id: flake8
+        additional_dependencies:
+          - flake8-bugbear==22.1.11
+          - flake8-comprehensions==3.8
+          - flake8-pytest-style==1.6
+          - flake8-spellcheck==0.24
+          - flake8-unused-arguments==0.0.9
+          - flake8-noqa==1.2.1
+          - pep8-naming==0.12.1
diff --git a/README.md b/README.md
index 1766c59..259ae18 100644
--- a/README.md
+++ b/README.md
@@ -37,7 +37,7 @@ imputed_df = imputer.get_result()
 In short, the following steps are executed under the hood when the imputr code is run:
 
 1. Make a deep copy of the given dataframe
-2. Classify column types based on name, frequencies and data type into: continuous, categorical or datetime
+2. Classify column types based on name, frequencies and data type into continuous or categorical 
 3. Determine execution order for imputation. Start with the column with the least missing values as target and end at the column with most missing values.
 4. Determine random forest depths for each column with [heuristic](http://linktopseudocodereadthedocs.io) algorithm
 5. Apply missForest algorithm for all columns: iteratively take the target columns from the execution order, temporarily impute other (unimputed) columns with mean/mode and train random forest to impute target. Iterate until converged.
diff --git a/datasets/DigiDB_digimonlist.csv b/datasets/DigiDB_digimonlist.csv
new file mode 100644
index 0000000..012caeb
--- /dev/null
+++ b/datasets/DigiDB_digimonlist.csv
@@ -0,0 +1,250 @@
+Number,Digimon,Stage,Type,Attribute,Memory,Equip Slots,Lv 50 HP,Lv50 SP,Lv50 Atk,Lv50 Def,Lv50 Int,Lv50 Spd
+1,Kuramon,Baby,Free,Neutral,2,0,590,77,79,69,68,95
+2,Pabumon,Baby,Free,Neutral,2,0,950,62,76,76,69,68
+3,Punimon,Baby,Free,Neutral,2,0,870,50,97,87,50,75
+4,Botamon,Baby,Free,Neutral,2,0,690,68,77,95,76,61
+5,Poyomon,Baby,Free,Neutral,2,0,540,98,54,59,95,86
+6,Koromon,In-Training,Free,Fire,3,0,940,52,109,93,52,76
+7,Tanemon,In-Training,Free,Plant,3,0,1030,64,85,82,73,69
+8,Tsunomon,In-Training,Free,Earth,3,0,930,54,107,92,54,76
+9,Tsumemon,In-Training,Free,Dark,3,0,930,64,108,64,54,93
+10,Tokomon,In-Training,Free,Neutral,3,0,640,86,76,74,74,103
+11,Nyaromon,In-Training,Free,Light,3,0,540,107,54,64,103,94
+12,Pagumon,In-Training,Free,Dark,3,0,550,103,60,63,102,93
+13,Yokomon,In-Training,Free,Plant,3,0,1040,64,82,82,75,69
+14,Bukamon,In-Training,Free,Water,3,0,830,93,54,74,103,69
+15,Motimon,In-Training,Free,Neutral,3,0,1030,63,82,81,78,69
+16,Wanyamon,In-Training,Free,Wind,3,0,830,82,79,75,75,82
+17,Agumon,Rookie,Vaccine,Fire,5,1,1030,59,131,103,54,86
+18,Agumon (Blk),Rookie,Virus,Fire,5,1,1020,56,124,108,56,85
+19,Armadillomon,Rookie,Free,Earth,4,1,1160,85,67,111,65,72
+20,Impmon,Rookie,Virus,Dark,5,1,530,114,83,65,114,102
+21,Elecmon,Rookie,Data,Electric,4,1,930,93,82,79,79,90
+22,Otamamon,Rookie,Virus,Water,4,1,930,105,52,75,113,78
+23,Gaomon,Rookie,Data,Neutral,5,1,1030,69,118,74,66,101
+24,Gazimon,Rookie,Virus,Dark,4,1,970,71,123,64,59,102
+25,Gabumon,Rookie,Data,Fire,5,1,980,88,94,81,79,91
+26,Gabumon (Blk),Rookie,Virus,Fire,5,1,950,83,99,89,74,91
+27,Guilmon,Rookie,Virus,Fire,5,1,1050,69,133,74,54,101
+28,Kudamon,Rookie,Vaccine,Light,5,1,590,128,53,74,117,105
+29,Keramon,Rookie,Free,Dark,5,1,1030,74,123,69,61,101
+30,Gotsumon,Rookie,Data,Earth,5,1,790,79,93,118,90,72
+31,Goblimon,Rookie,Virus,Earth,4,1,1050,51,115,110,51,84
+32,Gomamon,Rookie,Vaccine,Water,5,1,1160,69,93,93,81,79
+33,Syakomon,Rookie,Virus,Water,4,1,870,100,53,86,112,78
+34,Solarmon,Rookie,Vaccine,Light,4,1,1030,88,69,108,71,77
+35,Terriermon,Rookie,Vaccine,Wind,4,1,690,93,84,75,84,112
+36,Tentomon,Rookie,Vaccine,Plant,4,1,750,79,86,110,93,73
+37,ToyAgumon,Rookie,Vaccine,Neutral,4,1,1110,87,72,112,63,71
+38,Dorumon,Rookie,Data,Neutral,5,1,1020,65,128,79,56,101
+39,Hagurumon,Rookie,Virus,Electric,4,1,1090,91,66,110,69,71
+40,Patamon,Rookie,Data,Wind,4,1,880,93,79,74,92,90
+41,Hackmon,Rookie,Data,Fire,5,1,1030,59,118,108,63,85
+42,Palmon,Rookie,Data,Plant,5,1,1140,65,103,90,80,79
+43,DemiDevimon,Rookie,Virus,Dark,4,1,650,87,89,76,89,111
+44,Biyomon,Rookie,Vaccine,Wind,4,1,830,93,85,79,85,91
+45,Falcomon,Rookie,Vaccine,Wind,5,1,740,93,94,79,79,113
+46,Veemon,Rookie,Free,Neutral,5,1,1040,74,130,74,53,101
+47,Salamon,Rookie,Vaccine,Light,4,1,540,118,59,69,119,97
+48,Betamon,Rookie,Virus,Water,4,1,870,101,61,76,113,78
+49,Hawkmon,Rookie,Free,Wind,5,1,690,88,99,79,83,114
+50,Lalamon,Rookie,Data,Plant,4,1,1100,74,87,87,79,79
+51,Lucemon,Rookie,Vaccine,Light,14,1,1230,148,59,104,208,119
+52,Renamon,Rookie,Data,Plant,5,1,930,93,89,74,89,93
+53,Lopmon,Rookie,Data,Earth,4,1,790,79,103,68,103,85
+54,Wormmon,Rookie,Free,Plant,4,1,760,76,92,111,90,71
+55,IceDevimon,Champion,Virus,Water,8,1,990,94,140,89,118,92
+56,Aquilamon,Champion,Free,Wind,8,1,840,108,109,89,89,143
+57,Ankylomon,Champion,Free,Earth,6,2,1330,98,89,133,79,79
+58,Ikkakumon,Champion,Vaccine,Water,8,1,1330,84,118,102,94,90
+59,Wizardmon,Champion,Data,Dark,8,1,690,138,74,79,143,118
+60,Woodmon,Champion,Virus,Plant,6,2,1480,74,109,103,89,88
+61,ExVeemon,Champion,Free,Neutral,8,1,1030,118,104,94,94,118
+62,Angemon,Champion,Vaccine,Light,8,1,940,94,128,89,128,99
+63,Ogremon,Champion,Virus,Earth,8,1,1230,64,155,118,64,97
+64,Guardromon,Champion,Virus,Electric,6,2,990,84,108,138,103,79
+65,GaoGamon,Champion,Data,Wind,8,1,740,108,109,94,99,138
+66,Kabuterimon,Champion,Vaccine,Plant,6,2,890,89,108,128,116,81
+67,ShellNumemon,Champion,Virus,Water,6,2,1280,103,79,148,74,79
+68,Gargomon,Champion,Vaccine,Electric,6,2,1030,103,109,99,89,108
+69,Garurumon,Champion,Vaccine,Fire,8,1,890,108,99,94,94,138
+70,Garurumon (Blk),Champion,Virus,Fire,8,1,890,108,109,104,79,133
+71,Kyubimon,Champion,Data,Fire,8,1,740,138,59,84,138,128
+72,Growlmon,Champion,Virus,Fire,8,1,1180,79,143,113,69,109
+73,Kurisarimon,Champion,Free,Dark,8,1,1280,84,153,79,64,113
+74,Greymon,Champion,Vaccine,Fire,8,1,1230,74,148,118,64,104
+75,Greymon (Blue),Champion,Virus,Fire,8,1,1280,74,153,118,59,99
+76,Clockmon,Champion,Data,Electric,6,2,1030,118,64,101,133,92
+77,Kuwagamon,Champion,Virus,Plant,6,2,1180,69,153,113,59,99
+78,Gekomon,Champion,Virus,Water,6,2,1130,123,68,89,128,90
+79,Geremon,Champion,Virus,Electric,6,2,1380,99,104,128,64,78
+80,GoldNumemon,Champion,Virus,Light,6,2,1130,124,59,84,143,88
+81,Cyclonemon,Champion,Virus,Earth,6,2,940,84,131,128,93,81
+82,Sunflowmon,Champion,Data,Plant,6,2,1180,113,64,89,141,86
+83,Seadramon,Champion,Data,Water,6,2,1080,118,64,99,134,88
+84,GeoGreymon,Champion,Vaccine,Fire,8,1,1330,89,143,84,64,118
+85,Sukamon,Champion,Virus,Earth,6,2,1430,98,89,133,69,79
+86,Starmon,Champion,Data,Neutral,6,2,1080,98,104,109,91,101
+87,Stingmon,Champion,Free,Plant,8,1,1130,84,143,74,74,133
+88,Socerimon,Champion,Vaccine,Water,8,1,1030,123,64,93,148,90
+89,Tankmon,Champion,Data,Electric,6,2,940,84,113,141,98,81
+90,Tyrannomon,Champion,Data,Fire,6,2,1230,59,148,125,59,97
+91,Gatomon,Champion,Vaccine,Light,8,1,640,143,69,79,143,123
+92,Devimon,Champion,Virus,Dark,8,1,990,94,133,84,125,97
+93,Togemon,Champion,Data,Plant,8,1,1330,84,108,113,93,90
+94,Dorugamon,Champion,Data,Earth,8,1,1180,84,138,89,69,123
+95,Nanimon,Champion,Virus,Earth,6,2,1070,84,108,133,98,81
+96,Numemon,Champion,Virus,Earth,6,2,1380,99,84,138,69,83
+97,Birdramon,Champion,Vaccine,Fire,8,1,940,113,94,84,109,128
+98,Bakemon,Champion,Virus,Dark,6,2,590,148,64,74,138,128
+99,Veedramon,Champion,Vaccine,Wind,8,1,1180,84,138,113,64,114
+100,PlatinumSukamon,Champion,Virus,Neutral,6,2,1380,98,79,138,79,79
+101,BlackGatomon,Champion,Virus,Dark,8,1,690,133,84,84,133,118
+102,Vegiemon,Champion,Virus,Plant,6,2,1380,79,113,106,87,88
+103,Peckmon,Champion,Vaccine,Wind,8,1,790,113,104,84,94,148
+104,Meramon,Champion,Data,Fire,6,2,1130,69,138,113,79,99
+105,Frigimon,Champion,Vaccine,Water,6,2,1380,83,103,98,99,90
+106,Leomon,Champion,Vaccine,Earth,8,1,1180,69,143,123,71,97
+107,Reppamon,Champion,Vaccine,Light,8,1,790,118,94,99,99,143
+108,Waspmon,Champion,Virus,Electric,6,2,1180,74,133,99,74,113
+109,MegaKabuterimon,Ultimate,Vaccine,Plant,12,2,1430,115,94,163,109,92
+110,Antylamon,Ultimate,Data,Neutral,12,2,940,123,124,109,114,168
+111,Andromon,Ultimate,Vaccine,Electric,12,2,1040,94,133,157,133,95
+112,Meteormon,Ultimate,Data,Earth,12,2,1090,104,123,163,128,89
+113,Infermon,Ultimate,Free,Dark,14,1,1330,99,198,89,74,153
+114,Myotismon,Ultimate,Virus,Dark,14,1,1290,113,148,99,148,110
+115,AeroVeedramon,Ultimate,Vaccine,Wind,14,1,1430,94,163,99,94,153
+116,Etemon,Ultimate,Virus,Dark,12,2,1130,133,104,119,129,133
+117,Angewomon,Ultimate,Vaccine,Light,14,1,890,163,69,94,188,143
+118,Okuwamon,Ultimate,Virus,Plant,12,2,1330,74,158,158,74,119
+119,Garudamon,Ultimate,Vaccine,Fire,12,2,1040,123,124,109,129,143
+120,Gigadramon,Ultimate,Virus,Wind,12,2,1240,94,137,148,113,100
+121,CannonBeemon,Ultimate,Virus,Electric,12,2,990,123,129,139,99,143
+122,GrapLeomon,Ultimate,Vaccine,Electric,12,2,1580,89,163,99,79,143
+123,Cyberdramon,Ultimate,Vaccine,Dark,14,1,1480,81,173,143,79,122
+124,Shakkoumon,Ultimate,Free,Light,14,1,1530,135,84,158,139,92
+125,Cherrymon,Ultimate,Virus,Plant,12,2,1630,108,113,133,114,100
+126,Silphymon,Ultimate,Free,Wind,14,1,1040,138,119,119,124,158
+127,SuperStarmon,Ultimate,Data,Light,12,2,1180,128,122,134,109,120
+128,SkullGreymon,Ultimate,Virus,Dark,14,1,1230,79,203,153,69,119
+129,Zudomon,Ultimate,Vaccine,Water,12,2,1630,84,150,128,104,102
+130,Taomon,Ultimate,Data,Dark,12,2,990,148,69,104,173,138
+131,Chirinmon,Ultimate,Vaccine,Light,14,1,940,133,124,119,124,168
+132,Digitamamon,Ultimate,Data,Neutral,12,2,1380,89,128,148,111,102
+133,SkullMeramon,Ultimate,Data,Fire,12,2,1530,79,183,133,70,113
+134,ShogunGekomon,Ultimate,Virus,Water,12,2,1980,96,113,113,99,97
+135,DoruGreymon,Ultimate,Data,Fire,14,1,1480,84,161,153,84,116
+136,Knightmon,Ultimate,Data,Neutral,12,2,1140,109,135,158,123,92
+137,Datamon,Ultimate,Virus,Electric,12,2,1180,133,74,114,175,102
+138,Paildramon,Ultimate,Free,Neutral,14,1,1280,133,139,124,109,128
+139,Panjyamon,Ultimate,Vaccine,Water,12,2,1280,128,124,114,111,126
+140,Pumpkinmon,Ultimate,Data,Earth,10,3,1480,97,123,108,119,111
+141,Piximon,Ultimate,Data,Light,12,2,990,123,104,104,134,153
+142,BlackKingNumemon,Ultimate,Virus,Dark,10,3,1580,113,89,168,89,89
+143,BlueMeramon,Ultimate,Virus,Fire,12,2,1140,109,148,94,133,119
+144,Vademon,Ultimate,Virus,Dark,10,3,1130,165,64,94,173,97
+145,Whamon,Ultimate,Vaccine,Water,12,2,1680,93,123,123,124,100
+146,MagnaAngemon,Ultimate,Vaccine,Light,14,1,1180,143,98,119,163,105
+147,MachGaogamon,Ultimate,Data,Wind,12,2,1480,89,158,89,89,158
+148,Mamemon,Ultimate,Data,Earth,10,3,1480,113,111,153,84,97
+149,MegaSeadramon,Ultimate,Data,Water,12,2,1330,138,86,114,158,102
+150,Megadramon,Ultimate,Virus,Wind,12,2,1430,79,158,148,69,119
+151,WarGrowlmon,Ultimate,Virus,Fire,14,1,1430,84,178,138,87,116
+152,MetalGreymon,Ultimate,Vaccine,Fire,14,1,1530,84,168,148,80,113
+153,MetalGreymon (Blue),Ultimate,Virus,Fire,14,1,1670,84,173,143,69,110
+154,MetalTyrannomon,Ultimate,Virus,Electric,12,2,1090,104,130,178,118,92
+155,MetalMamemon,Ultimate,Data,Electric,10,3,1040,99,123,153,128,103
+156,Monzaemon,Ultimate,Vaccine,Neutral,12,2,1580,93,128,118,119,100
+157,Crowmon,Ultimate,Vaccine,Wind,12,2,890,128,119,104,119,173
+158,RizeGreymon,Ultimate,Vaccine,Fire,14,1,1530,94,178,109,69,143
+159,Lilamon,Ultimate,Data,Plant,12,2,1280,148,69,104,168,114
+160,Rapidmon,Ultimate,Vaccine,Electric,12,2,1180,113,114,109,119,143
+161,Lillymon,Ultimate,Data,Plant,12,2,890,153,74,94,163,158
+162,Lucemon FM,Ultimate,Virus,Neutral,22,1,1390,139,163,114,203,139
+163,LadyDevimon,Ultimate,Virus,Dark,14,1,890,163,99,94,158,143
+164,WereGarurumon,Ultimate,Vaccine,Earth,12,2,1430,89,178,89,79,153
+165,WereGarurumon (Blk),Ultimate,Virus,Earth,12,2,1480,79,183,104,69,148
+166,Wisemon,Ultimate,Virus,Dark,12,2,790,168,69,84,198,133
+167,Alphamon,Mega,Vaccine,Neutral,22,1,1390,128,158,183,158,130
+168,UlforceVeedramon,Mega,Vaccine,Wind,22,1,1680,129,188,109,104,198
+169,Ebemon,Mega,Virus,Electric,16,3,1230,178,74,114,198,129
+170,Imperialdramon DM,Mega,Free,Fire,20,2,1730,143,139,139,139,148
+171,Imperialdramon FM,Mega,Free,Neutral,20,2,1780,114,198,124,114,153
+172,Vikemon,Mega,Free,Water,18,3,1780,105,158,143,129,133
+173,VenomMyotismon,Mega,Virus,Dark,20,2,1540,120,193,104,148,138
+174,WarGreymon,Mega,Vaccine,Fire,20,2,1630,98,193,163,99,140
+175,Examon,Mega,Data,Wind,22,1,1630,148,174,129,129,153
+176,Ophanimon,Mega,Vaccine,Light,20,2,840,183,104,164,193,153
+177,Gaiomon,Mega,Virus,Fire,18,3,1630,99,203,129,94,158
+178,ChaosGallantmon,Mega,Virus,Dark,22,1,1340,139,178,139,163,144
+179,Gankoomon,Mega,Data,Fire,22,1,2080,90,188,163,109,138
+180,Kuzuhamon,Mega,Data,Dark,18,3,1380,163,84,129,193,139
+181,GranKuwagamon,Mega,Virus,Plant,18,3,1530,88,178,178,89,140
+182,GroundLocomon,Mega,Data,Electric,18,3,1140,114,144,213,133,128
+183,Craniamon,Mega,Vaccine,Earth,22,1,1630,143,124,208,134,124
+184,Kerpymon (Good),Mega,Vaccine,Light,20,1,1290,188,94,104,208,158
+185,SaberLeomon,Mega,Data,Wind,18,3,1680,99,228,104,84,163
+186,Sakuyamon,Mega,Data,Light,18,3,990,178,94,114,188,173
+187,Jesmon,Mega,Data,Neutral,22,1,1480,119,198,149,114,168
+188,ShineGreymon,Mega,Vaccine,Light,20,2,1880,114,203,109,84,158
+189,Justimon,Mega,Vaccine,Light,18,3,1530,99,193,158,99,144
+190,Kentaurosmon,Mega,Vaccine,Light,22,1,1140,153,139,154,154,183
+191,Seraphimon,Mega,Vaccine,Light,20,2,1480,162,94,144,198,135
+192,MegaGargomon,Mega,Vaccine,Electric,18,3,1430,132,149,139,119,144
+193,TigerVespamon,Mega,Virus,Electric,18,3,1630,104,193,114,99,173
+194,Titamon,Mega,Virus,Earth,18,3,1930,99,183,128,114,129
+195,Dianamon,Mega,Data,Water,20,1,790,178,89,154,198,183
+196,Diaboromon,Mega,Free,Dark,20,2,1680,114,243,104,79,173
+197,Creepymon,Mega,Virus,Dark,22,1,1440,133,183,114,183,140
+198,Gallantmon,Mega,Virus,Light,22,1,1480,148,149,154,149,148
+199,Dynasmon,Mega,Data,Wind,22,1,1680,114,213,129,94,178
+200,Leopardmon,Mega,Data,Light,22,1,990,188,124,114,183,188
+201,Leopardmon LM,Mega,Data,Earth,25,1,1290,153,159,129,139,218
+202,HiAndromon,Mega,Vaccine,Electric,18,3,1190,114,153,178,138,124
+203,Barbamon,Mega,Virus,Dark,22,1,1330,184,84,129,233,133
+204,BanchoLeomon,Mega,Vaccine,Earth,18,3,1630,84,193,188,80,138
+205,Piedmon,Mega,Virus,Dark,18,3,890,178,129,104,183,163
+206,Puppetmon,Mega,Virus,Plant,18,3,1140,114,163,163,148,124
+207,PlatinumNumemon,Mega,Virus,Neutral,16,3,1830,132,94,178,109,120
+208,BlackWarGreymon,Mega,Virus,Fire,20,2,1730,93,183,178,89,140
+209,PrinceMamemon,Mega,Data,Neutral,16,3,1630,137,104,173,109,130
+210,Plesiomon,Mega,Data,Water,18,3,1680,153,74,114,188,129
+211,HerculesKabuterimon,Mega,Vaccine,Plant,18,3,1680,128,114,168,124,124
+212,Beelzemon,Mega,Virus,Dark,22,1,1680,114,228,119,99,168
+213,Belphemon SM,Mega,Virus,Dark,22,1,1730,178,89,114,203,139
+214,Hououmon,Mega,Vaccine,Fire,18,3,1390,173,84,99,193,158
+215,Magnadramon,Mega,Vaccine,Light,20,2,1880,168,89,124,183,129
+216,Boltmon,Mega,Data,Electric,18,3,1580,83,198,163,84,140
+217,Mastemon,Mega,Vaccine,Neutral,22,1,1340,144,173,134,173,149
+218,MarineAngemon,Mega,Vaccine,Water,18,3,1190,198,64,89,203,153
+219,Minervamon,Mega,Virus,Neutral,20,1,1580,114,208,114,119,168
+220,MirageGaogamon,Mega,Data,Wind,18,3,1480,114,183,114,109,178
+221,Machinedramon,Mega,Virus,Electric,18,3,1240,114,173,183,128,124
+222,MetalEtemon,Mega,Virus,Earth,18,3,1630,128,134,188,89,124
+223,MetalGarurumon,Mega,Data,Water,18,3,1140,143,154,129,129,178
+224,MetalGarurumon (Blk),Mega,Virus,Electric,18,3,1190,128,163,173,133,130
+225,MetalSeadramon,Mega,Data,Water,18,3,1430,148,99,139,168,129
+226,RustTyranomon,Mega,Virus,Electric,18,3,1680,83,218,163,74,140
+227,Leviamon,Mega,Virus,Water,22,1,1730,120,168,158,144,133
+228,Lilithmon,Mega,Virus,Dark,22,1,940,203,99,104,223,173
+229,Ravemon,Mega,Vaccine,Wind,18,3,1040,143,139,119,139,203
+230,Crusadermon,Mega,Virus,Dark,22,1,1240,153,144,139,144,193
+231,Rosemon,Mega,Data,Plant,18,3,1330,147,144,129,149,144
+232,Lotosmon,Mega,Data,Plant,18,3,940,188,74,109,213,168
+233,Imperialdramon PM,Ultra,Vaccine,Light,25,1,1530,158,154,154,154,153
+234,Omnimon,Ultra,Vaccine,Light,25,1,1680,104,208,168,134,144
+235,Omnimon Zwart,Ultra,Vaccine,Dark,25,1,1490,139,153,193,158,134
+236,Belphemon RM,Ultra,Virus,Dark,25,0,1780,84,247,168,109,140
+237,Lucemon SM,Ultra,Virus,Dark,25,0,1490,173,89,124,233,158
+238,Flamedramon,Armor,Free,Fire,8,3,1130,93,119,99,89,138
+239,Magnamon,Armor,Free,Earth,22,2,1240,124,168,208,148,124
+240,Rapidmon (Armor),Armor,Vaccine,Light,18,3,1140,114,158,178,158,124
+241,Kerpymon (Blk),Mega,Virus,Dark,20,1,1290,188,94,94,223,153
+242,Beelzemon BM,Mega,Virus,Dark,25,1,1680,114,238,124,104,178
+243,Darkdramon,Mega,Virus,Electric,18,3,1580,94,188,148,99,139
+244,Chaosmon,Ultra,Vaccine,Neutral,25,2,1080,129,318,94,89,188
+245,Valkyrimon,Mega,Free,Wind,18,3,1330,139,148,129,129,168
+246,ShineGreymon BM,Mega,Vaccine,Fire,22,2,1980,114,228,104,84,168
+247,MirageGaogamon BM,Mega,Data,Light,20,2,1440,124,178,104,158,174
+248,Ravemon BM,Mega,Vaccine,Wind,20,2,1040,133,149,139,144,213
+249,Rosemon BM,Mega,Data,Plant,20,2,1480,143,149,139,159,143
diff --git a/imputr.code-workspace b/imputr.code-workspace
new file mode 100644
index 0000000..5e835fc
--- /dev/null
+++ b/imputr.code-workspace
@@ -0,0 +1,10 @@
+{
+	"settings": {
+		"jupyter.debugJustMyCode": false
+	},
+	"folders": [
+		{
+			"path": "."
+		}
+	]
+}
\ No newline at end of file
diff --git a/imputr/__init__.py b/imputr/__init__.py
new file mode 100644
index 0000000..7312ae5
--- /dev/null
+++ b/imputr/__init__.py
@@ -0,0 +1,1 @@
+from .imputers import *
\ No newline at end of file
diff --git a/imputr/_constants.py b/imputr/_constants.py
new file mode 100644
index 0000000..82c002f
--- /dev/null
+++ b/imputr/_constants.py
@@ -0,0 +1,4 @@
+"""
+Package metadata.
+"""
+__version__ = "0.1.0"
diff --git a/imputr/domain/__init__.py b/imputr/domain/__init__.py
new file mode 100644
index 0000000..eba21fa
--- /dev/null
+++ b/imputr/domain/__init__.py
@@ -0,0 +1,3 @@
+from .column import Column
+from .types import DataType
+from .table import Table
\ No newline at end of file
diff --git a/imputr/domain/column.py b/imputr/domain/column.py
new file mode 100644
index 0000000..d102b9a
--- /dev/null
+++ b/imputr/domain/column.py
@@ -0,0 +1,183 @@
+import numpy as np
+import pandas as pd
+from typing import Union
+from pandas.core.dtypes.common import is_numeric_dtype, is_object_dtype, is_string_dtype, is_categorical_dtype
+from sklearn.preprocessing import LabelEncoder
+from .types import DataType
+
+class Column:
+    """Data class that encapsulates the data and imputr-specific metadata of a column.
+
+    Parameters
+    ----------
+    data : pd.Series
+        The Pandas Series that contains the column data.
+    data_type : Union[str, DataType] (optional)
+        The imputr DataType specified per string or DataType enum class.
+    """
+
+    data: pd.Series
+    name: str
+    type: DataType
+    missing_value_count: int
+    unique_value_count: int
+    average: Union[bool, str, float]
+    _imputed_data: pd.Series
+    _label_encoder: LabelEncoder
+
+    def __init__(self, data: pd.Series, data_type: Union[str, DataType] = None):
+        self.data = data
+        self.name = data.name
+        self.missing_value_count = self._count_number_of_missing_values(data)
+        self.unique_value_count = self._count_number_of_unique_values(data)
+        self.type = self._infer_data_type(data, data_type)
+        self.average = self._compute_average(data, self.type)
+        self._imputed_data = None
+        self._label_encoder = LabelEncoder() if self.type is DataType.CATEGORICAL else None
+
+    @property
+    def imputed_data(self) -> pd.Series:
+        """Gets imputed data. 
+        
+        If the data has not been imputed by any strategy yet, it interally sets
+        the _imputed_data value as average-based imputed pd.Series (mode for
+        discrete and mean for continuous) and returns it.
+
+        Returns:
+            pd.Series: imputed data of the Column object.
+        """
+        if self._imputed_data is None:
+            self._imputed_data = self.data.fillna(self.average)
+        return self._imputed_data
+    
+    @imputed_data.setter
+    def imputed_data(self, column_values: pd.Series) -> None:
+        """Sets the imputed_data property.
+        
+        TODO: assert right dimension and non-nullness of the given imputed data.
+
+        Parameters
+        ----------
+        column_values : pd.Series
+            The pd.Series that contains the imputed data. 
+            Should not contains null-types or and have the same length as the original pd.Series.
+        """
+        self._imputed_data = column_values
+        
+    @property
+    def numeric_encoded_imputed_data(self) -> pd.Series:
+        """Gets the imputed-then-numerically-encoded data.
+        
+        Transforms categorical data types to incrementally labeled integer data.
+        Calls the property getter of self._imputed_data.
+
+        Returns:
+            pd.Series: _description_
+        """
+
+        if self.type is DataType.CONTINUOUS:
+            # Uses property getter here. Original data may need average imputation first.
+            return self.imputed_data
+        
+        return self._label_encoder.fit_transform(self.imputed_data)
+
+    @property
+    def null_indices(self) -> np.ndarray:
+        """Returns np.ndarray of indexes where a null value is found.
+        
+        Mutually exclusive with the non_null_indices property.
+
+        Returns:
+            np.ndarray: indexes where a null value is found
+        """
+        return np.where(pd.isnull(self.data))
+    
+    @property
+    def non_null_indices(self) -> np.ndarray:
+        """Returns np.ndarray of indexes where a non-null value is found.
+        
+        Mutually exclusive with the null_indices property.
+
+        Returns:
+            np.ndarray: indexes where a non-null value is found
+        """
+        return np.where(~pd.isnull(self.data))
+    
+        
+    def _infer_data_type(self, column_data: pd.Series, data_type: Union[str, DataType] = None) -> DataType:
+        """Helper method to infer the imputr-defined data type of a given column.
+
+        Parameters
+        ----------
+        column : pd.Series
+            The column for which the data type must be determined.
+        data_type : Union[str, DataType]
+            String or DataType enum representing imputr data type.
+
+        Returns
+        -------
+            DataType : The data type as modeled by the imputr library.
+        """
+        
+        if type(data_type) is str:
+            return DataType.str_to_data_type(data_type)
+
+        if type(data_type) is DataType:
+            return data_type
+        
+        if is_numeric_dtype(column_data.dtype):
+            return DataType.CONTINUOUS
+        
+        if True in {
+            is_object_dtype(column_data.dtype),
+            is_string_dtype(column_data.dtype),
+            is_categorical_dtype(column_data.dtype)}:
+            return DataType.CATEGORICAL
+        else:
+            raise TypeError(f'Column data type \'{column_data.dtype}\' is not supported.')
+
+
+    def _count_number_of_unique_values(self, column: pd.Series) -> int:
+        """
+        Counts the number of unique values in a column. Includes NaN in the count.
+
+        Returns
+        -------
+            int : the number of unique values in a column.
+        """
+        return column.nunique()
+
+
+    def _count_number_of_missing_values(self, column: pd.Series) -> int:
+        """
+        Counts the number of missing values in a column.
+
+        Returns
+        -------
+            int : the number of missing values in a column.
+        """
+        return column.isnull().sum()
+
+
+    def _compute_average(self, column: pd.Series, type: DataType) -> Union[str, float]:
+        """Calculates mode or mean of the given pd.Series.
+
+        If the column has a categorical type this method computes the mode of the column
+        If has a continuous type it calculates the mean.
+
+        Parameters
+        ----------
+            column : pd.Series
+                The Pandas Series that contains the column data.
+            type : DataType
+                The data type as modeled by the imputr library.
+
+        Returns
+        -------
+            Union[str, float] : Either the mode or the mean of the library.
+        """
+        if type is DataType.CATEGORICAL:
+            # Picks first mode in the list of possible modes
+            return str(column.mode().iloc[0])
+        else:
+            return float(column.mean())
\ No newline at end of file
diff --git a/imputr/domain/table.py b/imputr/domain/table.py
new file mode 100644
index 0000000..34e9db1
--- /dev/null
+++ b/imputr/domain/table.py
@@ -0,0 +1,51 @@
+from typing import Union
+import pandas as pd
+
+from ..domain import DataType
+from ..domain import Column
+
+class Table:
+    """Data class that encapsulates the data and imputr-specific metadata of a table.
+    
+
+    Parameters
+    ----------
+    data : pd.DataFrame
+        The Pandas DataFrame that contains the table data.
+        
+    predefined_datatypes : dict[str, Union[str, DataType]] (optional)
+        Dictionary that has column names as key and the data type as specified
+        in the Column constructor as value.
+    
+    """
+    
+    data: pd.DataFrame
+    columns: list[Column]
+    
+    def __init__(self,
+                 data: pd.DataFrame,
+                 predefined_datatypes: dict[str, Union[str, DataType]] = None):
+        self.columns = self._construct_columns(data, predefined_datatypes)
+    def _construct_columns(self, 
+                           data: pd.DataFrame,
+                           predefined_datatypes) -> list[Column]:
+        """
+        Loops over dataframe columns to construct Column objects.
+
+        Parameters
+        ----------
+        data : pd.DataFrame 
+            The Pandas DataFrame that contains the columns.
+            
+        predefined_datatypes : dict[str, Union[str, DataType]] (optional)
+            Dictionary that has column names as key and the data type as specified
+            in the Column constructor as value.
+
+        Returns
+        -------
+            List[Column] : the list of constructed Column objects.
+        """
+        return [Column(data.iloc[:, index]) if predefined_datatypes is None 
+                or item not in predefined_datatypes
+                else Column(data.iloc[:, index], predefined_datatypes[item]) 
+                for index, item in enumerate(data.columns)]
\ No newline at end of file
diff --git a/imputr/domain/types.py b/imputr/domain/types.py
new file mode 100644
index 0000000..bc3783f
--- /dev/null
+++ b/imputr/domain/types.py
@@ -0,0 +1,25 @@
+from enum import Enum
+
+class DataType(Enum):
+    """Enum class that represents the various data types that the library is able to
+    impute for and with.
+
+    Currently only contains categorical and continuous.
+    Future releases may contain specific enumertions for discrete, 
+    discrete-ordinal and a separate datetime type.
+    """
+
+    CATEGORICAL = (1,)
+    CONTINUOUS = 2
+    
+    @classmethod
+    def str_to_data_type(cls, string_name: str):
+        """Maps string to imputr DataType."""
+        
+        str_mapping = {
+            'cat': cls.CATEGORICAL,
+            'cont': cls.CONTINUOUS
+        } 
+        if string_name not in str_mapping:
+            raise ValueError(f'Data type with \'{string_name}\' string representation is not defined.')
+        return str_mapping[string_name]
\ No newline at end of file
diff --git a/imputr/imputers/__init__.py b/imputr/imputers/__init__.py
new file mode 100644
index 0000000..d5e37b1
--- /dev/null
+++ b/imputr/imputers/__init__.py
@@ -0,0 +1,2 @@
+from .autoimputer import AutoImputer
+from .meanimputer import MeanImputer
\ No newline at end of file
diff --git a/imputr/imputers/_base.py b/imputr/imputers/_base.py
new file mode 100644
index 0000000..4fcaa56
--- /dev/null
+++ b/imputr/imputers/_base.py
@@ -0,0 +1,242 @@
+from abc import ABC, abstractmethod
+from operator import attrgetter
+
+import pandas as pd
+
+from ..domain import Table, Column, DataType
+from ..strategy._base import _BaseStrategy
+from ..strategy.multivariate import _MultivariateStrategy
+from ..strategy import *
+from ..strategy.univariate import _UnivariateStrategy, MeanStrategy
+from typing import Union
+
+class _BaseImputer(ABC):
+    """Abstract base class for imputer classes.
+
+    This class contains a number of generic implementations that are relevant
+    for all Imputer subclasses. It also contains generic implementation of methods
+    that can be used by the subclasses, but may also be overwritten.
+    
+    Parameters
+    ----------
+    data : pd.DataFrame
+        The dataframe which undergoes imputation.
+    
+    predefined_datatypes : dict[str, Union[str, DataType]] (optional)
+        Dictionary that has column names as key and the data type as specified
+        in the Column constructor as value.
+
+    """
+    
+    table: Table
+    predefined_order: dict[str, int]
+    predefined_strategies: dict[str, dict]
+    
+    strategies: dict[str, _BaseStrategy]
+    ordered_columns: list[Column]
+    include_non_missing: bool
+            
+    def __init__(self,
+                 data: pd.DataFrame,
+                 predefined_datatypes: dict[str, Union[str, DataType]] = None):
+        self.table = Table(data, predefined_datatypes)
+        
+        
+        
+
+    @abstractmethod
+    def impute(self) -> pd.DataFrame:
+        """Imputes dataset as configured in the framework.
+
+        Returns
+        -------
+            pd.DataFrame: Imputed dataset.
+        """
+        return
+
+    def _determine_order(self, 
+                        columns: list[Column],
+                        predefined_strategies: dict[str, _BaseStrategy],
+                        predefined_order: dict[str, int] = None) -> list[Column]:
+        """
+        Determines the imputation order based on the predefined order, imputation
+        strategy type and the number of missing values. The algorithm looks at predefined
+        order first, then whether the column has a univariate or multivariate strategy
+        and finally the number of missing values. Ranks univariate strategies before multivariate 
+        strategies and less number of missing values before more number of missing values.
+
+        Parameters
+        ----------
+        columns : list[Column]
+            The columns that will undergo sequential imputation.
+            
+        predefined_strategies : dict[str, _BaseStrategy] 
+            Dictionary of of column names and their respective strategy that the 
+            imputer will use.
+
+        predefined_order : dict[str, int] (optional)
+            Dictionary of predefined order in which the imputation must be done.
+            
+        Returns
+        -------
+            List[Column] : returns list of Column references in imputation order.
+        """
+        
+        # TODO Implement assertion that order dict is incremental starting
+        # from 0 as such: { 'column_x': 0, 'column_z': 1, 'column_y': 2 }
+        
+        if predefined_order is not None:
+            columns_tup_with_ranking = []
+            for e in predefined_order.items():
+                tup = (next(filter(lambda x: x.name == e[0], columns)), e)
+                columns_tup_with_ranking.append(tup)
+            columns_tup_with_ranking = sorted(columns_tup_with_ranking, 
+                                                key=lambda x: x[1][1])
+            
+            columns_in_predefined_order = list(map(lambda x: x[0], columns_tup_with_ranking))
+                
+            columns = list(filter(lambda x: x.name not in predefined_order.keys(), columns))
+                
+        else:
+            columns_in_predefined_order = []
+        
+        multivariate_strat_cols = filter(lambda x: 
+                isinstance(predefined_strategies[x.name], _MultivariateStrategy), columns)
+        multivariate_strat_cols = sorted(multivariate_strat_cols, 
+                                         key=attrgetter('missing_value_count'),
+                                         reverse=True)
+        
+        univariate_strat_cols = filter(lambda x: 
+                isinstance(predefined_strategies[x.name], _UnivariateStrategy), columns)
+        
+        univariate_strat_cols = sorted(univariate_strat_cols, 
+                                       key=attrgetter('missing_value_count'),
+                                       reverse=True)
+        
+        return columns_in_predefined_order + univariate_strat_cols + multivariate_strat_cols 
+
+    def _determine_list_of_included_columns(self, 
+                                           predefined_strategies: dict[str, dict] = None, 
+                                           predefined_order: dict[str, int] = None, 
+                                           include_non_missing: bool = False) -> list[Column]:
+        """Determines list of columns that need fitting of imputation strategies.
+        
+        By default includes all columns that have missing value, a defined strategy or defined order.
+        
+        Parameters
+        ----------
+        predefined_datatypes : dict[str, Union[str, DataType]] (optional)
+            Dictionary that has column names as key and the data type as specified
+            in the Column constructor as value.
+            
+        predefined_order : dict[int, str] (optional)
+            Contains predefined order as defined in public API. Defaults to None
+            
+        include_non_missing : bool
+            Boolean flag that describes whether all column need fitting.
+            Defaults to None.
+
+        Returns:
+            list[Column]: List of columns that need strategy fitting.
+        """
+        
+        if include_non_missing == True:
+            return self.table.columns
+        else:
+            predefined_strategies = {} if predefined_strategies is None else predefined_strategies
+            predefined_order = {} if predefined_order is None else predefined_order
+            return list(filter(lambda x: x.missing_value_count > 0 
+                   or x.name in predefined_strategies 
+                   or x.name in predefined_order, self.table.columns))
+            
+    def _construct_strategies(self, 
+                        default_strategy: _BaseStrategy,
+                        predefined_strategies: dict[str, dict] = None
+                        ) -> dict[str, _BaseStrategy]:
+        """Constructs strategies to prepare for fitting and imputation.
+        
+        Parameters
+        ----------
+        strategies : dict[str, dict] (optional)
+            Contains name - dict as defined in public API. Defaults to None.
+
+        Returns:
+            dict[str, _BaseStrategy]: Contains strategy for each column.
+        """
+        if predefined_strategies is None: 
+            predefined_strategies = {}
+        
+        constructed_strategies: dict[str, _BaseStrategy] = {}
+        
+        for col in self.included_columns:
+            feature_columns = list(filter(lambda x: x.name != col.name, self.table.columns))
+            if col.name in predefined_strategies:
+                strategy_kwargs = predefined_strategies[col.name]
+                # Get strategy class to be constructed from string mapping
+                strategy_cls = self.str_to_strategy(strategy_kwargs['strategy'])
+                if 'params' in strategy_kwargs:
+                    strategy_params = strategy_kwargs['params']
+                else:
+                    strategy_params = {}
+                # Construct imputation strategy class and append
+                if issubclass(strategy_cls, _MultivariateStrategy):
+                    constructed_strategies[col.name] = strategy_cls.from_dict(col, 
+                                                                        feature_columns,
+                                                                        **strategy_params)
+                if issubclass(strategy_cls, _UnivariateStrategy):
+                    constructed_strategies[col.name] = strategy_cls.from_dict(col, 
+                                                                            **strategy_params)
+            else:
+                if issubclass(default_strategy, _MultivariateStrategy):
+                    constructed_strategies[col.name] = default_strategy(target_column=col,
+                                                                        feature_columns=feature_columns)
+                if issubclass(default_strategy, _UnivariateStrategy):
+                    constructed_strategies[col.name] = default_strategy(target_column=col)
+                        
+        return constructed_strategies
+
+    def str_to_strategy(self, string_name: str) -> _BaseStrategy:
+        """Returns the strategy class type for given string abbreviation.
+
+        Parameters
+        ----------
+        string_name : str
+            The string abbrevaion of the imputation strategy.
+
+        Returns
+        -------
+            _BaseStrategy : the imputation strategy class type.
+        """
+        
+        
+        str_to_strategy_mapping = {
+            'rf': RandomForestStrategy,
+            'mean': MeanStrategy,
+            # 'median': MedianStrategy,
+            # 'mode': ModeStrategy,
+        }
+        
+        if string_name not in str_to_strategy_mapping:
+                raise ValueError(f'Strategy with \'{string_name}\' string representation is not defined.')
+        return str_to_strategy_mapping[string_name]
+
+    
+    def impute(self) -> pd.DataFrame:
+        """Imputes dataframe with specified strategies.
+
+        Returns:
+            pd.DataFrame: imputed dataset.
+        """
+        
+        imputed_df = pd.DataFrame()
+        
+        for col in self.ordered_columns:
+            strategy = self.strategies[col.name]
+            strategy.fit()
+            imputed_series = strategy.impute_column()
+            
+            #TODO Measure time the complexity of this operation
+            col.imputed_data = imputed_series
+            imputed_df[col.name] = imputed_series
+        
+        return imputed_df.reindex(list(map(lambda x: x.name, self.table.columns)), axis=1)
\ No newline at end of file
diff --git a/imputr/imputers/autoimputer.py b/imputr/imputers/autoimputer.py
new file mode 100644
index 0000000..fd1339f
--- /dev/null
+++ b/imputr/imputers/autoimputer.py
@@ -0,0 +1,53 @@
+from ..domain import Column, DataType
+from ._base import _BaseImputer
+import pandas as pd
+from ..strategy._base import _BaseStrategy
+from ..strategy.multivariate import RandomForestStrategy
+from typing import Union
+
+
+class AutoImputer(_BaseImputer):
+    """Automatic imputation class that implements the RandomForest strategy 
+    as main imputation method. Can be configured to implement other strategies
+    for specific columns and a custom imputation order.
+    
+    Parameters
+    ----------
+    data : pd.DataFrame
+        The dataframe which undergoes imputation.
+        
+    predefined_order : dict[int, str] (optional)
+        Dictionary of column names and their order for imputation. 
+        Keys must be incremental starting from zero: 0, 1, 2
+        
+    predefined_strategies : dict[str, dict] (optional)
+        Dictionary of column name and strategy kwargs.
+    
+    predefined_datatypes : dict[str, Union[str, DataType]] (optional)
+        Dictionary that has column names as key and the data type as specified
+        in the Column constructor as value.
+        
+    include_non_missing : bool (optional)
+        Flag to indicate whether columns without missing value need fitting 
+        of strategies. Default is set to False.
+
+    """
+    
+    predefined_order: dict[str, int]
+    strategies: dict[str, _BaseStrategy]
+    ordered_columns: list[Column]
+    include_non_missing: bool
+    
+    def __init__(self, 
+                 data: pd.DataFrame,
+                 predefined_order: dict[str, int] = None,
+                 predefined_strategies: dict[str, dict] = None,
+                 predefined_datatypes: dict[str, Union[str, DataType]] = None,
+                 include_non_missing: bool = False,
+                 ):
+        super().__init__(data, predefined_datatypes)
+        self.included_columns = self._determine_list_of_included_columns(predefined_strategies, 
+                                                                        predefined_order, 
+                                                                        include_non_missing)
+        self.strategies = self._construct_strategies(RandomForestStrategy, predefined_strategies)
+        self.ordered_columns = self._determine_order(self.included_columns, self.strategies, predefined_order)
\ No newline at end of file
diff --git a/imputr/imputers/meanimputer.py b/imputr/imputers/meanimputer.py
new file mode 100644
index 0000000..7f0db33
--- /dev/null
+++ b/imputr/imputers/meanimputer.py
@@ -0,0 +1,53 @@
+from typing import Union
+from ..domain import Column, DataType
+from ._base import _BaseImputer
+import pandas as pd
+from ..strategy._base import _BaseStrategy
+from ..strategy.univariate import MeanStrategy
+
+class MeanImputer(_BaseImputer):
+    """Simple imputation class that uses average imputation
+    as main imputation method. Uses mode for categorical and mean for continuous
+    columns. Can be configured to implement other strategies for specific columns 
+    and a custom imputation order.
+    
+    Parameters
+    ----------
+    data : pd.DataFrame
+        The dataframe which undergoes imputation.
+        
+    predefined_order : dict[int, str] (optional)
+        Dictionary of column names and their order for imputation. 
+        Keys must be incremental starting from zero: 0, 1, 2
+        
+    predefined_strategies : dict[str, dict] (optional)
+        Dictionary of column name and strategy kwargs.
+    
+    predefined_datatypes : dict[str, Union[str, DataType]] (optional)
+        Dictionary that has column names as key and the data type as specified
+        in the Column constructor as value.
+        
+    include_non_missing : bool (optional)
+        Flag to indicate whether columns without missing value need fitting 
+        of strategies. Default is set to False.
+    """
+    
+    predefined_order: dict[str, int]
+    predefined_strategies: dict[str, dict]
+    strategies: dict[str, _BaseStrategy]
+    ordered_columns: list[Column]
+    include_non_missing: bool
+    
+    def __init__(self, 
+                 data: pd.DataFrame,
+                 predefined_order: dict[str, int] = None,
+                 predefined_strategies: dict[str, dict] = None,
+                 predefined_datatypes: dict[str, Union[str, DataType]] = None,
+                 include_non_missing: bool = False,
+                 ):
+        super().__init__(data)
+        self.included_columns = self._determine_list_of_included_columns(predefined_strategies, 
+                                                                        predefined_order, 
+                                                                        include_non_missing)
+        self.strategies = self._construct_strategies(MeanStrategy, predefined_strategies)
+        self.ordered_columns = self._determine_order(self.included_columns, self.strategies, predefined_order)
diff --git a/imputr/strategy/__init__.py b/imputr/strategy/__init__.py
new file mode 100644
index 0000000..4102633
--- /dev/null
+++ b/imputr/strategy/__init__.py
@@ -0,0 +1,2 @@
+from .multivariate import RandomForestStrategy
+from .univariate import MeanStrategy
\ No newline at end of file
diff --git a/imputr/strategy/_base.py b/imputr/strategy/_base.py
new file mode 100644
index 0000000..d24220b
--- /dev/null
+++ b/imputr/strategy/_base.py
@@ -0,0 +1,74 @@
+from abc import ABC, abstractmethod
+import pandas as pd
+from ..domain import DataType
+
+from ..domain import Column
+
+class _BaseStrategy(ABC):
+    """Abstract base class for strategy classes.
+
+    This class contains the abstract methods that define the interface for all
+    strategy classes which implement an imputer strategy.
+
+    Parameters
+    ----------
+    data : pd.DataFrame
+        This is the Pandas DataFrame that contains the tabular dataset which
+        undergoes imputation. This can be used to configure the imputer.
+
+    index : int
+        The column index of the target column.
+    """
+
+    target_column: Column
+
+    def __init__(self, target_column: Column):
+        self.target_column = target_column     
+           
+    @classmethod   
+    @abstractmethod
+    def from_dict(cls, 
+                  target_column: Column,
+                  **kwargs: dict):
+        """Class constructor that uses the dictionary to build strategy.
+        
+        Uses a part of the dictionary given to imputer constructor.
+
+        Args:
+            target_column (Column): Column that needs imputation by strategy.
+        """
+        return
+    
+    @property
+    @abstractmethod
+    def supported_data_types(self) -> list[DataType]:
+        """The imputer data types that are supported by 
+        this imputation strategy.
+
+        Returns
+        -------
+        list[DataType] : List of imputr DataType enums.
+        """
+        return 
+        
+
+    @abstractmethod
+    def fit(self) -> None:
+        """Executes the necessary preparation steps for imputation.
+
+        This method may train a machine learning model, fit a distribution,
+        compute statistics in order to make it ready for imputation.
+        """
+        return
+
+    @abstractmethod
+    def impute_column(self) -> pd.Series:
+        """Runs imputer strategy on the target column.
+
+        This method fills all missing values with its own strategy.
+
+        Returns
+        -------
+        pd.Series : The Pandas Series that contains that has the imputed column values.
+        """
+        return
diff --git a/imputr/strategy/multivariate.py b/imputr/strategy/multivariate.py
new file mode 100644
index 0000000..683fe3a
--- /dev/null
+++ b/imputr/strategy/multivariate.py
@@ -0,0 +1,194 @@
+from abc import  abstractmethod
+from typing import Union
+from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier
+import pandas as pd
+from ..domain import Column, DataType
+from ._base import _BaseStrategy
+import numpy as np
+
+
+class _MultivariateStrategy(_BaseStrategy):
+    """
+    The abstract class that contains the interface for multivariate imputation
+    strategies.
+    """
+    
+    feature_columns: list[Column]
+    _feature_df: pd.DataFrame
+    
+    def __init__(self, 
+                 target_column: Column, 
+                 feature_columns: list[Column]
+                 ):
+        super().__init__(target_column)
+        self.feature_columns = feature_columns
+        
+    @classmethod   
+    @abstractmethod
+    def from_dict(cls, 
+                  target_column: Column, 
+                  feature_columns: list[Column],
+                  **kwargs: dict):
+        return
+    
+    def _create_df_from_num_encoded_feature_columns(self, feature_columns: 
+                                                list[Column]) -> pd.DataFrame:
+        """Creates pd.DataFrame from pd.Series objects that contain
+        the numerically encoded imputed data for the respective column.
+
+        Returns:
+            pd.DataFrame : joined dataframe of num-encoded and imputed data.
+        """
+        
+        df_dict = {}
+        for col in feature_columns:
+            df_dict[col.name] = col.numeric_encoded_imputed_data
+        return pd.DataFrame(df_dict)
+
+class RandomForestStrategy(_MultivariateStrategy):
+    """
+    Strategy implementation for RandomForest-based imputation.
+        
+    Parameters
+    ----------
+    target_column : Column
+        The column which needs imputation.
+    
+    feature_columns : list[Column]
+        The predictor columns for the Random Forest to train on.
+    
+    data_type : Union[str, DataType] (optional)
+        The string or enum representation of the data_type.
+        
+    n_estimators : int (optional)
+        Number of decision trees used in the forest. Please refer ...
+        
+    max_depth : int (optional)
+        Maximum depth of decision trees used in the forest. Please refer ...
+    
+    min_sample_split : int (optional)
+        Minimum sample split of decision trees.  Please refer ...
+    
+    min_samples_leaf : int (optional)
+        Minimum samples at leaves of decision trees. Please refer ...
+    
+    min_weight_fraction_leaf : float (optional)
+        Minimum weight fractions of leaves of decision trees. Please refer...
+        
+    max_features : Union[str, float] (optional)
+        Max features used per decision tree. Can be fraction or identifier like `sqrt`.
+        Please refer...
+        
+    max_leaf_nodes : int (optional)
+        Max number of nodes at leaves of the decision trees. Please refer...
+    
+    """
+    
+    supported_data_types: list = [
+        DataType.CATEGORICAL,
+        DataType.CONTINUOUS
+        ]
+
+    def __init__(self, 
+                 target_column: Column, 
+                 feature_columns: list[Column],
+                 n_estimators: int = 64,
+                 max_depth:int =8,
+                 min_sample_split: int  = 512,
+                 min_samples_leaf: int = 128,
+                 min_weight_fraction_leaf: float = 0.35,
+                 max_features: Union[str, float] = "sqrt",
+                 max_leaf_nodes: int = 32
+                 ):
+        super().__init__(target_column, feature_columns)
+        
+        if target_column.type not in self.supported_data_types:
+            raise ValueError(f'Data type {self.data_type} not supported by Random Forest.')
+
+        self.n_estimators = n_estimators
+        self.max_depth = max_depth
+        self.min_sample_split = min_sample_split
+        self.min_samples_leaf = min_samples_leaf
+        self.min_weight_fraction_leaf = min_weight_fraction_leaf
+        self.max_features = max_features
+        self.max_leaf_nodes = max_leaf_nodes
+        self.data_type = target_column.type
+       
+    @classmethod
+    def from_dict(cls, 
+                  target_column: Column, 
+                  feature_columns: list[Column], 
+                  **kwargs: dict):
+        return cls(
+            target_column, 
+            feature_columns, 
+            n_estimators = kwargs.get('n_estimators', 64),
+            max_depth = kwargs.get('max_depth', 8),
+            min_sample_split = kwargs.get('min_sample_split', 512),
+            min_samples_leaf = kwargs.get('min_samples_leaf', 128),
+            min_weight_fraction_leaf = kwargs.get('min_weight_fraction_leaf', 0.35),
+            max_features = kwargs.get('max_features', 'sqrt'),
+            max_leaf_nodes = kwargs.get('max_leaf_nodes', 32)
+        )
+
+    def fit(self) -> None:
+        """Fits RandomForest to make ready for imputation.
+        
+        Looks at DataType to determine if it needs a Regressor or Classifier. 
+        The scikit APIs are the same for both models, which is why we use the 
+        `estimator_cls` variable.
+        """
+        if self.data_type == DataType.CONTINUOUS:
+            estimator_cls = RandomForestRegressor
+
+        if self.data_type == DataType.CATEGORICAL:
+            estimator_cls = RandomForestClassifier
+
+        self.impute_strategy = estimator_cls(n_estimators=self.n_estimators,
+                                          max_depth=self.max_depth,
+                                          min_samples_leaf=self.min_samples_leaf,
+                                          min_samples_split=self.min_sample_split,
+                                          min_weight_fraction_leaf=self.min_weight_fraction_leaf,
+                                          max_features=self.max_features,
+                                          max_leaf_nodes=self.max_leaf_nodes
+                                          )
+        
+        # Get full feature DF, train on rows where target column is not null.
+        self._feature_df = self._create_df_from_num_encoded_feature_columns(self.feature_columns)
+        
+        feature_df_where_not_null = self._feature_df.iloc[self.target_column.non_null_indices]
+        target_where_not_null = self.target_column.data.iloc[self.target_column.non_null_indices]
+        self.impute_strategy.fit(feature_df_where_not_null, target_where_not_null)
+    
+    def impute_column(self) -> pd.Series:
+        """Imputes all null values with the Random Forest and unions with non-null values.
+    
+        TODO: Refactor this in general method for better reuse.
+        
+        Returns
+        -------
+            pd.Series: fully imputed data column.
+        """
+        
+        feature_df_where_null = self._feature_df.iloc[self.target_column.null_indices]
+        if len(feature_df_where_null) == 0:
+            predictions_ndarray = np.empty(0)
+        else:
+            predictions_ndarray = self.impute_strategy.predict(feature_df_where_null)
+                    
+        # Create data frame from predictions (single column dataframe)
+        predictions_frame = pd.DataFrame(predictions_ndarray, columns=[self.target_column.name])
+        predictions_frame['index'] = self.target_column.null_indices[0]
+        predictions_frame.set_index('index', inplace=True)
+        
+        # Create data frame from existing non-null values (single column dataframe)
+        target_where_not_null = self.target_column.data.iloc[self.target_column.non_null_indices]
+        target_column_where_not_null_frame = pd.DataFrame(target_where_not_null.to_numpy(), 
+                                                          columns=[self.target_column.name])
+        target_column_where_not_null_frame['index'] = self.target_column.non_null_indices[0]
+        target_column_where_not_null_frame.set_index('index', inplace=True)
+        
+        # Union data together and return pd.Series containing fully imputed data.
+        concatenated_frame = pd.concat([target_column_where_not_null_frame, predictions_frame]) \
+            .sort_index(axis=0)
+        return concatenated_frame[self.target_column.name]
\ No newline at end of file
diff --git a/imputr/strategy/univariate.py b/imputr/strategy/univariate.py
new file mode 100644
index 0000000..79c4c7c
--- /dev/null
+++ b/imputr/strategy/univariate.py
@@ -0,0 +1,62 @@
+from abc import abstractmethod
+from ..domain import Column
+from ._base import _BaseStrategy
+import pandas as pd
+from ..domain import DataType
+
+
+class _UnivariateStrategy(_BaseStrategy):
+    """
+    The abstract class that contains the interface for univariate imputation
+    strategies.
+    """
+
+    def __init__(self, 
+                 target_column: Column
+                 ):
+        super().__init__(target_column)
+        
+    @classmethod   
+    @abstractmethod
+    def from_dict(cls, 
+                  target_column: Column,
+                  **kwargs: dict):
+        return
+    
+class MeanStrategy(_UnivariateStrategy):
+    """
+    Mean imputation strategy. Imputes calculated mean for numeric columns
+    and median for categoric columns.
+    """
+
+    supported_data_types: list = [
+        DataType.CATEGORICAL,
+        DataType.CONTINUOUS
+        ]
+
+    def __init__(self,
+                 target_column: Column
+                 ):
+        super().__init__(target_column)
+
+    @classmethod
+    def from_dict(cls, 
+                  target_column: Column,
+                  **kwargs: dict):
+        return cls(target_column)
+    
+    def fit(self) -> None:
+        """
+        Gets mean or median value from Column to be imputed.
+        """
+        
+        self.mean = self.target_column.average
+    
+    def impute_column(self) -> pd.Series:
+        """Imputes column with mean value.
+
+        Returns
+        -------
+            pd.Series: fully imputed data column.
+        """
+        return self.target_column.data.fillna(self.mean)
\ No newline at end of file
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..23d1cdc
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,53 @@
+[project]
+name = "imputr"
+description = 'Imputing missing values in your data science.'
+readme = "README.md"
+license = { file="LICENSE" }
+maintainers = [
+  { name = "Rauf Akdemir", email = "rauf@gmail.com" },
+  { name = "Stijn IJzermans", email = "stijn@stijzermans.nl" },
+]
+urls.Homepage = "https://github.com/imputr/imputr"
+urls.Source = "https://github.com/imputr/imputr"
+urls.Tracker = "https://github.com/imputr/imputr/issues"
+requires-python = ">=3.7"
+dependencies = [
+  "numpy>=1.23.0",
+  "pandas>=1.4.3",
+]
+classifiers = [
+   'Development Status :: 2 - Pre-Alpha',
+    'Programming Language :: Python :: 3.7',
+    'Programming Language :: Python :: 3.8',
+    'Programming Language :: Python :: 3.9',
+    'Programming Language :: Python :: 3.10',
+    'Intended Audience :: Developers',
+    'Intended Audience :: Science/Research',
+    'License :: OSI Approved :: Apache Software License',
+    'Operating System :: Unix',
+    'Operating System :: POSIX :: Linux',
+    'Topic :: Software Development :: Libraries :: Python Modules',
+    'Topic :: Internet',
+]
+dynamic = ["version"]
+
+[project.optional-dependencies]
+test = [
+  "pytest>=6",
+  "pytest-cov>=2.7",
+  "pytest-mock>=3.6",
+]
+
+[tool.hatch.version]
+path = "imputr/_constants.py"
+
+[tool.black]
+line-length = 120
+
+[tool.isort]
+profile = "black"
+known_first_party = ["imputr"]
+
+[build-system]
+requires = ["hatchling"]
+build-backend = "hatchling.build"


Test patch:
diff --git a/datasets/unittestsets/DigiDB_digimonlist_small.csv b/datasets/unittestsets/DigiDB_digimonlist_small.csv
new file mode 100644
index 0000000..77f5303
--- /dev/null
+++ b/datasets/unittestsets/DigiDB_digimonlist_small.csv
@@ -0,0 +1,6 @@
+Number,Digimon,Stage,Type,Attribute,Lv 50 HP,Lv50 SP,Lv50 Atk,Lv50 Def
+1,Kuramon,Baby,Free,Water,590,77,79,69
+2,Pabumon,Rookie,Data,Neutral,950,62,,76
+3,Punimon,,Data,Neutral,870,50,,87
+4,Botamon,Rookie,Free,,690,68,77,95
+5,Poyomon,Baby,Free,Water,540,98,54,59
\ No newline at end of file
diff --git a/test.ipynb b/test.ipynb
new file mode 100644
index 0000000..9777d2d
--- /dev/null
+++ b/test.ipynb
@@ -0,0 +1,346 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "import numpy as np\n",
+    "from imputr import AutoImputer\n",
+    "\n",
+    "df = pd.read_csv('datasets/DigiDB_digimonlist.csv')\n",
+    "\n",
+    "# Create random missing values\n",
+    "np.random.seed(100)\n",
+    "mask = np.random.choice([True, False], size=df.shape)\n",
+    "df.mask(mask, inplace=True)\n",
+    "\n",
+    "imputer = AutoImputer(df)\n",
+    "imputed_df = imputer.impute()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>Number</th>\n",
+       "      <th>Digimon</th>\n",
+       "      <th>Stage</th>\n",
+       "      <th>Type</th>\n",
+       "      <th>Attribute</th>\n",
+       "      <th>Memory</th>\n",
+       "      <th>Equip Slots</th>\n",
+       "      <th>Lv 50 HP</th>\n",
+       "      <th>Lv50 SP</th>\n",
+       "      <th>Lv50 Atk</th>\n",
+       "      <th>Lv50 Def</th>\n",
+       "      <th>Lv50 Int</th>\n",
+       "      <th>Lv50 Spd</th>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>index</th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "      <th></th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>124.642175</td>\n",
+       "      <td>Silphymon</td>\n",
+       "      <td>Baby</td>\n",
+       "      <td>Free</td>\n",
+       "      <td>Neutral</td>\n",
+       "      <td>2.000000</td>\n",
+       "      <td>1.617187</td>\n",
+       "      <td>1171.997575</td>\n",
+       "      <td>109.500384</td>\n",
+       "      <td>121.911932</td>\n",
+       "      <td>115.772511</td>\n",
+       "      <td>68.000000</td>\n",
+       "      <td>120.035061</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>124.642175</td>\n",
+       "      <td>Silphymon</td>\n",
+       "      <td>Mega</td>\n",
+       "      <td>Free</td>\n",
+       "      <td>Dark</td>\n",
+       "      <td>11.916496</td>\n",
+       "      <td>0.000000</td>\n",
+       "      <td>1171.997575</td>\n",
+       "      <td>62.000000</td>\n",
+       "      <td>121.911932</td>\n",
+       "      <td>115.772511</td>\n",
+       "      <td>111.538451</td>\n",
+       "      <td>68.000000</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>3.000000</td>\n",
+       "      <td>Punimon</td>\n",
+       "      <td>Mega</td>\n",
+       "      <td>Virus</td>\n",
+       "      <td>Neutral</td>\n",
+       "      <td>11.916496</td>\n",
+       "      <td>1.617187</td>\n",
+       "      <td>870.000000</td>\n",
+       "      <td>50.000000</td>\n",
+       "      <td>97.000000</td>\n",
+       "      <td>87.000000</td>\n",
+       "      <td>50.000000</td>\n",
+       "      <td>120.035061</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>124.642175</td>\n",
+       "      <td>Botamon</td>\n",
+       "      <td>Baby</td>\n",
+       "      <td>Free</td>\n",
+       "      <td>Neutral</td>\n",
+       "      <td>2.000000</td>\n",
+       "      <td>0.000000</td>\n",
+       "      <td>690.000000</td>\n",
+       "      <td>68.000000</td>\n",
+       "      <td>121.911932</td>\n",
+       "      <td>95.000000</td>\n",
+       "      <td>76.000000</td>\n",
+       "      <td>61.000000</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>124.642175</td>\n",
+       "      <td>Silphymon</td>\n",
+       "      <td>Baby</td>\n",
+       "      <td>Free</td>\n",
+       "      <td>Dark</td>\n",
+       "      <td>2.000000</td>\n",
+       "      <td>1.617187</td>\n",
+       "      <td>540.000000</td>\n",
+       "      <td>98.000000</td>\n",
+       "      <td>54.000000</td>\n",
+       "      <td>115.772511</td>\n",
+       "      <td>111.538451</td>\n",
+       "      <td>86.000000</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>...</th>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "      <td>...</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>244</th>\n",
+       "      <td>124.642175</td>\n",
+       "      <td>Silphymon</td>\n",
+       "      <td>Mega</td>\n",
+       "      <td>Virus</td>\n",
+       "      <td>Wind</td>\n",
+       "      <td>11.916496</td>\n",
+       "      <td>3.000000</td>\n",
+       "      <td>1171.997575</td>\n",
+       "      <td>109.500384</td>\n",
+       "      <td>148.000000</td>\n",
+       "      <td>129.000000</td>\n",
+       "      <td>129.000000</td>\n",
+       "      <td>120.035061</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>245</th>\n",
+       "      <td>246.000000</td>\n",
+       "      <td>Silphymon</td>\n",
+       "      <td>Mega</td>\n",
+       "      <td>Vaccine</td>\n",
+       "      <td>Fire</td>\n",
+       "      <td>11.916496</td>\n",
+       "      <td>1.617187</td>\n",
+       "      <td>1980.000000</td>\n",
+       "      <td>114.000000</td>\n",
+       "      <td>228.000000</td>\n",
+       "      <td>104.000000</td>\n",
+       "      <td>84.000000</td>\n",
+       "      <td>120.035061</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>246</th>\n",
+       "      <td>247.000000</td>\n",
+       "      <td>MirageGaogamon BM</td>\n",
+       "      <td>Mega</td>\n",
+       "      <td>Virus</td>\n",
+       "      <td>Light</td>\n",
+       "      <td>11.916496</td>\n",
+       "      <td>1.617187</td>\n",
+       "      <td>1440.000000</td>\n",
+       "      <td>124.000000</td>\n",
+       "      <td>121.911932</td>\n",
+       "      <td>104.000000</td>\n",
+       "      <td>158.000000</td>\n",
+       "      <td>120.035061</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>247</th>\n",
+       "      <td>124.642175</td>\n",
+       "      <td>Silphymon</td>\n",
+       "      <td>Mega</td>\n",
+       "      <td>Virus</td>\n",
+       "      <td>Wind</td>\n",
+       "      <td>20.000000</td>\n",
+       "      <td>2.000000</td>\n",
+       "      <td>1171.997575</td>\n",
+       "      <td>109.500384</td>\n",
+       "      <td>149.000000</td>\n",
+       "      <td>115.772511</td>\n",
+       "      <td>144.000000</td>\n",
+       "      <td>213.000000</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>248</th>\n",
+       "      <td>249.000000</td>\n",
+       "      <td>Silphymon</td>\n",
+       "      <td>Mega</td>\n",
+       "      <td>Data</td>\n",
+       "      <td>Plant</td>\n",
+       "      <td>20.000000</td>\n",
+       "      <td>1.617187</td>\n",
+       "      <td>1171.997575</td>\n",
+       "      <td>109.500384</td>\n",
+       "      <td>149.000000</td>\n",
+       "      <td>115.772511</td>\n",
+       "      <td>111.538451</td>\n",
+       "      <td>143.000000</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "<p>249 rows × 13 columns</p>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "           Number            Digimon Stage     Type Attribute     Memory  \\\n",
+       "index                                                                      \n",
+       "0      124.642175          Silphymon  Baby     Free   Neutral   2.000000   \n",
+       "1      124.642175          Silphymon  Mega     Free      Dark  11.916496   \n",
+       "2        3.000000            Punimon  Mega    Virus   Neutral  11.916496   \n",
+       "3      124.642175            Botamon  Baby     Free   Neutral   2.000000   \n",
+       "4      124.642175          Silphymon  Baby     Free      Dark   2.000000   \n",
+       "...           ...                ...   ...      ...       ...        ...   \n",
+       "244    124.642175          Silphymon  Mega    Virus      Wind  11.916496   \n",
+       "245    246.000000          Silphymon  Mega  Vaccine      Fire  11.916496   \n",
+       "246    247.000000  MirageGaogamon BM  Mega    Virus     Light  11.916496   \n",
+       "247    124.642175          Silphymon  Mega    Virus      Wind  20.000000   \n",
+       "248    249.000000          Silphymon  Mega     Data     Plant  20.000000   \n",
+       "\n",
+       "       Equip Slots     Lv 50 HP     Lv50 SP    Lv50 Atk    Lv50 Def  \\\n",
+       "index                                                                 \n",
+       "0         1.617187  1171.997575  109.500384  121.911932  115.772511   \n",
+       "1         0.000000  1171.997575   62.000000  121.911932  115.772511   \n",
+       "2         1.617187   870.000000   50.000000   97.000000   87.000000   \n",
+       "3         0.000000   690.000000   68.000000  121.911932   95.000000   \n",
+       "4         1.617187   540.000000   98.000000   54.000000  115.772511   \n",
+       "...            ...          ...         ...         ...         ...   \n",
+       "244       3.000000  1171.997575  109.500384  148.000000  129.000000   \n",
+       "245       1.617187  1980.000000  114.000000  228.000000  104.000000   \n",
+       "246       1.617187  1440.000000  124.000000  121.911932  104.000000   \n",
+       "247       2.000000  1171.997575  109.500384  149.000000  115.772511   \n",
+       "248       1.617187  1171.997575  109.500384  149.000000  115.772511   \n",
+       "\n",
+       "         Lv50 Int    Lv50 Spd  \n",
+       "index                          \n",
+       "0       68.000000  120.035061  \n",
+       "1      111.538451   68.000000  \n",
+       "2       50.000000  120.035061  \n",
+       "3       76.000000   61.000000  \n",
+       "4      111.538451   86.000000  \n",
+       "...           ...         ...  \n",
+       "244    129.000000  120.035061  \n",
+       "245     84.000000  120.035061  \n",
+       "246    158.000000  120.035061  \n",
+       "247    144.000000  213.000000  \n",
+       "248    111.538451  143.000000  \n",
+       "\n",
+       "[249 rows x 13 columns]"
+      ]
+     },
+     "execution_count": 2,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "imputed_df"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3.9.12 ('.venv': venv)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.9.12"
+  },
+  "orig_nbformat": 4,
+  "vscode": {
+   "interpreter": {
+    "hash": "b749e987cabc368a9a100edd9de6cab5c866b24e6a4e434db56032cbeee10200"
+   }
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_auto_imputer.py b/tests/test_auto_imputer.py
new file mode 100644
index 0000000..71abad7
--- /dev/null
+++ b/tests/test_auto_imputer.py
@@ -0,0 +1,83 @@
+import pandas as pd
+from imputr.imputers.autoimputer import AutoImputer
+from imputr.strategy.univariate import MeanStrategy
+from imputr.strategy.multivariate import RandomForestStrategy
+
+df = pd.read_csv('datasets/unittestsets/DigiDB_digimonlist_small.csv')
+    
+def test_ctor():
+    imputer = AutoImputer(df)
+    
+    assert len(imputer.strategies.items()) == 3
+    assert isinstance(imputer.strategies['Lv50 Atk'], RandomForestStrategy)
+    assert isinstance(imputer.strategies['Attribute'], RandomForestStrategy)
+    assert isinstance(imputer.strategies['Stage'], RandomForestStrategy)
+
+    assert hasattr(imputer.strategies['Stage'], 'impute_strategy') is False
+    assert hasattr(imputer.strategies['Lv50 Atk'], 'impute_strategy') is False
+    
+
+def test_ctor_include_non_missing():   
+    imputer = AutoImputer(df, include_non_missing=True)
+    
+    for col_name, strat in imputer.strategies.items():
+        assert isinstance(strat, RandomForestStrategy)
+        assert hasattr(strat, 'impute_strategy') is False
+    
+    
+    
+def test_ctr_strategies_with_dict_init():
+    predefined_strategies = {
+        'Number': {
+            'strategy':'mean'
+            },
+        'Lv50 Atk':  {
+            'strategy':'rf'
+        }
+    }
+    
+    imputer = AutoImputer(data=df,predefined_strategies=predefined_strategies, include_non_missing=True)
+    
+    assert isinstance(imputer.strategies['Number'], MeanStrategy)
+    assert isinstance(imputer.strategies['Lv50 Atk'], RandomForestStrategy)
+    assert isinstance(imputer.strategies['Attribute'], RandomForestStrategy)
+    assert isinstance(imputer.strategies['Type'], RandomForestStrategy)
+    
+    assert hasattr(imputer.strategies['Number'], 'impute_strategy') is False
+    assert hasattr(imputer.strategies['Lv50 Atk'], 'impute_strategy') is False
+    
+
+
+def test_ctor_with_dict_init_params():
+
+    predefined_strategies = {
+        'Number': {
+            'strategy':'mean'
+            },
+        'Lv50 Atk':  {
+            'strategy':'rf',
+            'params' : {
+                'n_estimators': 30,
+                'max_leaf_nodes': 10
+            }
+        }
+    }
+    
+    imputer = AutoImputer(data=df,predefined_strategies=predefined_strategies)
+    
+    assert isinstance(imputer.strategies['Number'], MeanStrategy)
+    assert isinstance(imputer.strategies['Lv50 Atk'], RandomForestStrategy)
+    assert imputer.strategies['Lv50 Atk'].n_estimators == 30
+    assert imputer.strategies['Lv50 Atk'].max_leaf_nodes == 10
+    
+    assert hasattr(imputer.strategies['Number'], 'impute_strategy') is False
+    assert hasattr(imputer.strategies['Lv50 Atk'], 'impute_strategy') is False
+
+
+def test_impute_columns():
+    assert df.isnull().values.any() == True
+    
+    imputer = AutoImputer(df, include_non_missing=True)
+    imputed_df = imputer.impute()
+    
+    assert imputed_df.isnull().values.any() == False
\ No newline at end of file
diff --git a/tests/test_base.py b/tests/test_base.py
new file mode 100644
index 0000000..6f05b0d
--- /dev/null
+++ b/tests/test_base.py
@@ -0,0 +1,58 @@
+import pandas as pd
+from imputr import AutoImputer
+from imputr.strategy import MeanStrategy
+import numpy as np
+
+df = pd.read_csv('datasets/unittestsets/DigiDB_digimonlist_small.csv')
+
+
+def test_construct_columns():
+    imputer = AutoImputer(df)
+    columns = imputer.table.columns
+    
+    numbers_column = next(filter(lambda x: x.name == 'Number', columns))
+
+    assert numbers_column.data.size == 5
+    assert np.array_equal(numbers_column.data, df['Number'])
+    
+    numbers_column = next(filter(lambda x: x.name == 'Lv50 Atk', columns))
+
+    assert numbers_column.data.size == 5
+    assert np.array_equal(numbers_column.data, df['Lv50 Atk'], equal_nan=True)
+    
+def test_determine_order_multivariate():
+    imputer = AutoImputer(df, include_non_missing=True)
+    
+    order = imputer.ordered_columns
+    
+    assert len(order) == 9
+    assert order[0].name == 'Lv50 Atk'
+    assert order[1].name == 'Stage'
+
+
+def test_determine_order_univariate_and_multivariate():
+    imputer = AutoImputer(df, include_non_missing=True, predefined_strategies={"Number": {"strategy":"mean"}})
+    
+    order = imputer.ordered_columns
+    
+    assert len(order) == 9
+    assert order[0].name == 'Number'
+
+def test_determine_order_univariate_and_multivariate_and_predefined_order():
+    imputer = AutoImputer(df, 
+                          include_non_missing=True, 
+                          predefined_order={'Type': 1, 'Attribute' : 0},
+                          predefined_strategies={"Number": {"strategy":"mean"}}
+                          )
+    columns = imputer.table.columns
+    numbers_column = next(filter(lambda x: x.name == 'Number', columns))
+    
+    imputer.strategies['Number'] = MeanStrategy(numbers_column)
+    
+    order = imputer.ordered_columns
+    
+    assert len(order) == 9
+    assert order[0].name == 'Attribute'
+    assert order[1].name == 'Type'
+    assert order[2].name == 'Number'
+    
\ No newline at end of file
diff --git a/tests/test_column.py b/tests/test_column.py
new file mode 100644
index 0000000..bcb389f
--- /dev/null
+++ b/tests/test_column.py
@@ -0,0 +1,77 @@
+"""
+Tests for Column data class.
+"""
+
+import pytest
+from imputr.domain import DataType, Column
+import pandas as pd
+from numpy.testing import assert_array_almost_equal
+import numpy as np
+
+def test_ctor_cont():
+    int_series = pd.Series([1,2,None,3])
+    int_series.name = 'int_col'
+
+    cont_col = Column(int_series)
+
+    assert cont_col.data.equals(int_series)
+    assert cont_col.name == 'int_col'
+    assert cont_col.missing_value_count == 1
+    assert cont_col.unique_value_count == 3
+    assert cont_col.type == DataType.CONTINUOUS
+    assert cont_col.average == 2
+    
+    assert_array_almost_equal(
+        np.asarray([[2]]),
+        cont_col.null_indices)
+    
+    assert_array_almost_equal(
+        np.asarray([[0,1,3]]),
+        cont_col.non_null_indices)
+    
+    assert_array_almost_equal(
+        pd.Series([1,2,2,3]).to_numpy(),
+        cont_col.imputed_data.to_numpy())
+    
+    cont_col.imputed_data = pd.Series([1,1,1,1])
+    
+    assert_array_almost_equal(
+        pd.Series([1,1,1,1]).to_numpy(),
+        cont_col.imputed_data.to_numpy())
+    
+    assert_array_almost_equal(
+        pd.Series([1,1,1,1]).to_numpy(),
+        cont_col.numeric_encoded_imputed_data.to_numpy())
+    
+def test_ctor_cat():
+    str_series = pd.Series(['a','a','b',None])
+    str_series.name = 'str_col'
+
+    cont_col = Column(str_series)
+
+    assert cont_col.data.equals(str_series)
+    assert cont_col.name == 'str_col'
+    assert cont_col.missing_value_count == 1
+    assert cont_col.unique_value_count == 2
+    assert cont_col.type == DataType.CATEGORICAL
+    assert cont_col.average == 'a'
+    
+    assert_array_almost_equal(
+        pd.Series([0,0,1,0]).to_numpy(),
+        cont_col.numeric_encoded_imputed_data)
+    
+def test_ctor_predefined_datatype():
+    int_series = pd.Series([1,2,2,3])
+    int_series.name = 'int_col'
+    
+    cat_col = Column(int_series, DataType.CATEGORICAL)
+    
+    assert cat_col.type == DataType.CATEGORICAL
+
+def test_ctor_predefined_datatype_str():
+    int_series = pd.Series([1,2,2,3])
+    int_series.name = 'int_col'
+    
+    cat_col = Column(int_series, 'cat')
+    
+    assert cat_col.type == DataType.CATEGORICAL
\ No newline at end of file
diff --git a/tests/test_mean_imputer.py b/tests/test_mean_imputer.py
new file mode 100644
index 0000000..8f48e99
--- /dev/null
+++ b/tests/test_mean_imputer.py
@@ -0,0 +1,82 @@
+import pandas as pd
+from imputr import MeanImputer
+from imputr.strategy import MeanStrategy, RandomForestStrategy
+
+df = pd.read_csv('datasets/unittestsets/DigiDB_digimonlist_small.csv')
+    
+def test_ctor():
+    imputer = MeanImputer(df)
+    
+    assert len(imputer.strategies.items()) == 3
+    assert isinstance(imputer.strategies['Lv50 Atk'], MeanStrategy)
+    assert isinstance(imputer.strategies['Attribute'], MeanStrategy)
+    assert isinstance(imputer.strategies['Stage'], MeanStrategy)
+
+    assert hasattr(imputer.strategies['Stage'], 'impute_strategy') is False
+    assert hasattr(imputer.strategies['Lv50 Atk'], 'impute_strategy') is False
+    
+
+def test_ctor_include_non_missing():   
+    imputer = MeanImputer(df, include_non_missing=True)
+    
+    for col_name, strat in imputer.strategies.items():
+        assert isinstance(strat, MeanStrategy)
+        assert hasattr(strat, 'impute_strategy') is False
+    
+    
+    
+def test_ctr_strategies_with_dict_init():
+    predefined_strategies = {
+        'Number': {
+            'strategy':'rf'
+            },
+        'Lv50 Atk':  {
+            'strategy':'mean'
+        }
+    }
+    
+    imputer = MeanImputer(data=df,predefined_strategies=predefined_strategies, include_non_missing=True)
+    
+    assert isinstance(imputer.strategies['Number'], RandomForestStrategy)
+    assert isinstance(imputer.strategies['Lv50 Atk'], MeanStrategy)
+    assert isinstance(imputer.strategies['Attribute'], MeanStrategy)
+    assert isinstance(imputer.strategies['Type'], MeanStrategy)
+    
+    assert hasattr(imputer.strategies['Number'], 'impute_strategy') is False
+    assert hasattr(imputer.strategies['Lv50 Atk'], 'impute_strategy') is False
+    
+
+
+def test_ctor_with_dict_init_params():
+
+    predefined_strategies = {
+        'Number': {
+            'strategy':'mean'
+            },
+        'Lv50 Atk':  {
+            'strategy':'rf',
+            'params' : {
+                'n_estimators': 30,
+                'max_leaf_nodes': 10
+            }
+        }
+    }
+    
+    imputer = MeanImputer(data=df,predefined_strategies=predefined_strategies)
+    
+    assert isinstance(imputer.strategies['Number'], MeanStrategy)
+    assert isinstance(imputer.strategies['Lv50 Atk'], RandomForestStrategy)
+    assert imputer.strategies['Lv50 Atk'].n_estimators == 30
+    assert imputer.strategies['Lv50 Atk'].max_leaf_nodes == 10
+    
+    assert hasattr(imputer.strategies['Number'], 'impute_strategy') is False
+    assert hasattr(imputer.strategies['Lv50 Atk'], 'impute_strategy') is False
+
+
+def test_impute_columns():
+    assert df.isnull().values.any() == True
+    
+    imputer = MeanImputer(df, include_non_missing=True)
+    imputed_df = imputer.impute()
+    
+    assert imputed_df.isnull().values.any() == False
\ No newline at end of file
diff --git a/tests/test_multivariate.py b/tests/test_multivariate.py
new file mode 100644
index 0000000..14393fc
--- /dev/null
+++ b/tests/test_multivariate.py
@@ -0,0 +1,78 @@
+"""
+Tests for multivariate strategies.
+"""
+
+from imputr.domain import Column
+from imputr.strategy import RandomForestStrategy
+import pandas as pd
+import numpy as np
+from imputr.domain import DataType
+from sklearn.utils.validation import check_is_fitted
+
+
+df = pd.read_csv('datasets/unittestsets/DigiDB_digimonlist_small.csv')
+    
+columns = [Column(df.iloc[:, index]) for index, item in enumerate(df.columns)]
+
+target_column_attribute = next(filter(lambda x: x.name == 'Attribute', columns))
+feature_columns_attribute = list(filter(lambda x: x.name != target_column_attribute.name, columns))
+
+target_column_lv50atk = next(filter(lambda x: x.name == 'Lv50 Atk', columns))
+feature_columns_lv50atk = list(filter(lambda x: x.name != target_column_lv50atk.name, columns))
+
+def test_rf_strategy_ctor():
+    strategy = RandomForestStrategy(target_column=target_column_attribute, 
+                                    feature_columns=feature_columns_attribute)
+
+    assert strategy.data_type == DataType.CATEGORICAL
+    assert strategy.target_column == target_column_attribute
+    assert strategy.n_estimators == 64
+    assert strategy.max_depth == 8
+    
+def test_rf_strategy_dict_ctor():
+    
+    ctor_dict = {
+        'n_estimators': 32,
+        'max_depth': 6
+    }
+    
+    strategy = RandomForestStrategy.from_dict(target_column_attribute, feature_columns_attribute, **ctor_dict)
+    
+    assert strategy.data_type == DataType.CATEGORICAL
+    assert strategy.target_column == target_column_attribute
+    assert strategy.n_estimators == 32
+    assert strategy.max_depth == 6
+
+def test_rf_strategy_fit():
+    strategy_attribute = RandomForestStrategy(target_column=target_column_attribute, 
+                                    feature_columns=feature_columns_attribute)
+    strategy_attribute.fit()
+    
+    check_is_fitted(strategy_attribute.impute_strategy)
+    
+    strategy_lv50atk = RandomForestStrategy(target_column=target_column_lv50atk, 
+                                    feature_columns=feature_columns_lv50atk)
+    strategy_lv50atk.fit()
+
+    check_is_fitted(strategy_lv50atk.impute_strategy)
+    
+
+def test_rf_strategy_impute_column():
+    strategy_attribute = RandomForestStrategy(target_column=target_column_attribute, 
+                                    feature_columns=feature_columns_attribute)
+    strategy_attribute.fit()
+    
+    strategy_attribute.impute_column()
+    
+    assert np.size(target_column_attribute.imputed_data) == target_column_attribute.data.size
+    assert np.count_nonzero(pd.isna(target_column_lv50atk.imputed_data)) == 0
+    
+    
+    strategy_lv50atk = RandomForestStrategy(target_column=target_column_lv50atk, 
+                                    feature_columns=feature_columns_lv50atk)
+    strategy_lv50atk.fit()
+    
+    strategy_lv50atk.impute_column()
+    
+    assert np.size(target_column_lv50atk.imputed_data) == target_column_lv50atk.data.size
+    assert np.count_nonzero(pd.isna(target_column_lv50atk.imputed_data)) == 0
diff --git a/tests/test_table.py b/tests/test_table.py
new file mode 100644
index 0000000..1427e02
--- /dev/null
+++ b/tests/test_table.py
@@ -0,0 +1,29 @@
+"""
+Tests for Table data class.
+"""
+
+import pandas as pd
+from imputr.domain import Table, DataType
+
+df = pd.read_csv('datasets/unittestsets/DigiDB_digimonlist_small.csv')
+
+def test_ctor_simple():
+   table = Table(df)
+   
+   assert len(table.columns) == 9
+   
+   for col in table.columns:
+       assert col.data.size == 5
+       
+
+def test_ctor_with_datatypes():
+   table = Table(df, {"Lv 50 Atk": 'cat'})
+   
+   assert len(table.columns) == 9
+   
+   for col in table.columns:
+       assert col.data.size == 5
+       if col.name == 'Lv 50 Atk':
+           assert col.type == DataType.CATEGORICAL
+    
+    
\ No newline at end of file
diff --git a/tests/test_types.py b/tests/test_types.py
new file mode 100644
index 0000000..bec5117
--- /dev/null
+++ b/tests/test_types.py
@@ -0,0 +1,16 @@
+"""
+Tests for types logic.
+"""
+
+import pytest
+from imputr.domain import DataType
+
+def test_correct_mapping_cat():
+    assert DataType.str_to_data_type('cat') == DataType.CATEGORICAL
+
+def test_correct_mapping_cont():
+    assert DataType.str_to_data_type('cont') == DataType.CONTINUOUS
+    
+def test_non_existent_str_mapping():
+    with pytest.raises(ValueError):
+        DataType.str_to_data_type('non_existent_mapping')
\ No newline at end of file
diff --git a/tests/utils/asserts.py b/tests/utils/asserts.py
new file mode 100644
index 0000000..e69de29