Repository: visualpython/visualpython (language: python)

Issue:
Implement 'Statistic Package'
T-test, P-value ... etc included.


Discussion / hints:
**- Progress Status on 25th Jul**
My team(Mr. Hyun-sub, Ho-jin, Jae-young) are preparing structure of statistics package 
for visual python and making list of stat methods.
Also for benchmarking other GUI based solutions such as SPSS, Rapidminer, Brightics Studio,
we are summarizing statistics method included in these solutions.

We plan to finish this work until this week and start to do make sample code for practice.

[Statistics Method List_Brightics Studio.xlsx](https://github.com/visualpython/visualpython/files/9180891/Statistics.Method.List_Brightics.Studio.xlsx)
[SPOILER_UNF_Statistics_Method_Rapidminer_Studio.xlsx](https://github.com/visualpython/visualpython/files/9180893/SPOILER_UNF_Statistics_Method_Rapidminer_Studio.xlsx)


**-Progress Status on 7th August**

My team(Mr. Jae-young, Ho-jin, Hyun-sub) are focusing on fundamental statistical methods down below. 
1. Descriptive Statistics
2. Statistical Estimation
3. Statistical Analysis
4. Statistical Hypothesis Test

We are implementing basic functions of those categories starting from 8/1. Our first target is to implement ‘Analysis’. 
Since the logics are quite similar, the UI structure will be similar with ‘Machine learning-Regression section”
Our target is to make a package that convert those statistical functions above into Pandas dataframe, finally into Python codes.
(We will consider Numpy later)

Our task is 

1. find the intersection between statistical function of various solution 
2. make an app implement

We are now holding task No.1 and focusing on task No.2, by getting experimental appliance on visualpython js, html code.
We plan to make a sample code of Analysis part.
-Progress Status on 8th August
My team(Mr.Ho-jin,Jae-young,Hyun-sub) has finished to list the benchmarked statistics GUI based solution packages.
After listing the whole packages, we found intersection of packages, which implies popular statistical methods that we should take focus upon them.
We are developing sample apps now, and are going to develop into real app according to the chart below, prioritised by green part of the chart.

[Statistics_Method.xlsx](https://github.com/visualpython/visualpython/files/9277199/Statistics_Method.xlsx)

Fix patch:
diff --git a/visualpython/css/component/dataSelector.css b/visualpython/css/component/dataSelector.css
index 530a67bd..1322e916 100644
--- a/visualpython/css/component/dataSelector.css
+++ b/visualpython/css/component/dataSelector.css
@@ -1,18 +1,15 @@
 /* DataSelector target*/
 .vp-ds-box {
-    display: inline-block;
+    display: inline-flex;
 }
 .vp-ds-filter {
     position: relative;
-    /* width: 20px;
-    height: 20px; */
-    right: 25px;
+    right: 20px;
     cursor: pointer;
-    /* LAB: img to background-image */
-    display: inline-block;
     background: center / contain no-repeat url(../../img/filter.svg);
     width: 12px;
     height: 12px;
+    top: 9px;
 }
 .vp-ds-box input.vp-ds-target {
     padding-right: 23px;
diff --git a/visualpython/css/component/multiSelector.css b/visualpython/css/component/multiSelector.css
index 3d6fb3ae..d0dee6fb 100644
--- a/visualpython/css/component/multiSelector.css
+++ b/visualpython/css/component/multiSelector.css
@@ -5,7 +5,7 @@
     grid-template-columns: calc(47% - 15px) 50px calc(47% - 15px);
     grid-auto-rows: 100%;
 }
-.vp-cs-select-search {
+.vp-cs-select-container input.vp-cs-select-search {
     width: 100%;
 }
 .vp-cs-select-search::after {
diff --git a/visualpython/css/component/popupComponent.css b/visualpython/css/component/popupComponent.css
index 97a1c407..6aa3efbf 100644
--- a/visualpython/css/component/popupComponent.css
+++ b/visualpython/css/component/popupComponent.css
@@ -274,7 +274,8 @@
     padding: 1px 8px 0 5px;
 }
 /* checkbox */
-.vp-popup-frame input[type=checkbox]:not(.vp-checkbox) {
+.vp-popup-frame input[type=checkbox]:not(.vp-checkbox),
+.vp-popup-frame input[type=radio]:not(.vp-radio) {
 	position: absolute;
 	width: 1px;
 	height: 1px;
@@ -285,7 +286,9 @@
 	border: 0;
 }
 .vp-popup-frame input[type=checkbox]:not(.vp-checkbox) + label,
-.vp-popup-frame label input[type=checkbox]:not(.vp-checkbox) + span {
+.vp-popup-frame input[type=radio]:not(.vp-radio) + label,
+.vp-popup-frame label input[type=checkbox]:not(.vp-checkbox) + span,
+.vp-popup-frame label input[type=radio]:not(.vp-radio) + span {
 	display: inline-block;
 	position: relative;
 	padding-left: 20px;
@@ -295,7 +298,9 @@
     vertical-align: middle;
 }
 .vp-popup-frame input[type=checkbox]:not(.vp-checkbox):disabled + label,
-.vp-popup-frame label input[type=checkbox]:not(.vp-checkbox):disabled + span {
+.vp-popup-frame input[type=radio]:not(.vp-radio):disabled + label,
+.vp-popup-frame label input[type=checkbox]:not(.vp-checkbox):disabled + span,
+.vp-popup-frame label input[type=radio]:not(.vp-radio):disabled + span {
 	color: var(--vp-gray-color);
 }
 .vp-popup-frame input[type=checkbox]:not(.vp-checkbox) + label::before,
@@ -322,7 +327,7 @@
 	width: 15px;
 	height: 15px;
 	background: url(../../img/checkbox_checked.svg);
-	background-size: 14px 14px;
+	background-size: 15px 15px;
 	background-repeat: no-repeat;
 	border: none;
 	box-sizing: border-box;
@@ -336,7 +341,50 @@
 	width: 15px;
 	height: 15px;
 	background: url(../../img/checkbox_unchecked.svg);
-	background-size: 14px 14px;
+	background-size: 15px 15px;
+	background-repeat: no-repeat;
+	border: none;
+	box-sizing: border-box;
+}
+.vp-popup-frame input[type=radio]:not(.vp-radio) + label::before,
+.vp-popup-frame label input[type=radio]:not(.vp-radio) + span::before {
+	content: '';
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 15px;
+	height: 15px;
+	text-align: center;
+	background: url(../../img/radio_unchecked.svg);
+	background-size: 15px 15px;
+	background-repeat: no-repeat;
+	border: none;
+	box-sizing: border-box;
+}
+.vp-popup-frame input[type=radio]:not(.vp-radio):checked + label::before,
+.vp-popup-frame label input[type=radio]:not(.vp-radio):checked + span::before {
+    content: '';
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 15px;
+	height: 15px;
+	background: url(../../img/radio_checked.svg);
+	background-size: 15px 15px;
+	background-repeat: no-repeat;
+	border: none;
+	box-sizing: border-box;
+}
+.vp-popup-frame input[type=radio]:not(.vp-radio):disabled + label::before,
+.vp-popup-frame label input[type=radio]:not(.vp-radio):disabled + span::before {
+    content: '';
+	position: absolute;
+	left: 0;
+	top: 0;
+	width: 15px;
+	height: 15px;
+	background: url(../../img/radio_unchecked.svg);
+	background-size: 15px 15px;
 	background-repeat: no-repeat;
 	border: none;
 	box-sizing: border-box;
diff --git a/visualpython/css/m_apps/frame.css b/visualpython/css/m_apps/frame.css
index 84c90413..905150d7 100644
--- a/visualpython/css/m_apps/frame.css
+++ b/visualpython/css/m_apps/frame.css
@@ -256,6 +256,10 @@
 .vp-inner-popup-addtype {
     width: 153px;
 }
+.vp-inner-popup-condition-use-text {
+    position: sticky;
+    left: 190px;
+}
 .vp-inner-popup-delete-value {
     display: inline-block;
     cursor: pointer;
@@ -294,7 +298,6 @@
 .vp-inner-popup-sortby-down {
     float: right;
     display: inline-block;
-
 }
 
 /* UDF Editor - CodeMirror */
diff --git a/visualpython/css/m_stats/descStats.css b/visualpython/css/m_stats/descStats.css
new file mode 100644
index 00000000..ec75a3ec
--- /dev/null
+++ b/visualpython/css/m_stats/descStats.css
@@ -0,0 +1,36 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : descStats.css
+ *    Author          : Black Logic
+ *    Note            : stylesheet for descStats.html
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 05. 31
+ *    Change Date     :
+ */
+.vp-percentile-box {
+    width: 300px;
+    height: 100px;
+    border: 0.25px solid var(--vp-border-gray-color);
+    padding: 0 5px;
+}
+.vp-percentile-item {
+    height: 30px;
+    width: 100%;
+    border-bottom: 0.25px solid var(--vp-border-gray-color);
+    padding: 0 10px;
+    line-height: 30px;
+    display: flex;
+    column-gap: 5px;
+    align-items: center;
+}
+.vp-percentile-value {
+    width: 95%;
+}
+.vp-percentile-box:not(.disabled) .vp-percentile-remove {
+    cursor: pointer;
+}
+.vp-percentile-box.disabled {
+    background-color: var(--vp-light-gray-color);
+    cursor: not-allowed;
+}
\ No newline at end of file
diff --git a/visualpython/css/m_stats/probDist.css b/visualpython/css/m_stats/probDist.css
new file mode 100644
index 00000000..e69de29b
diff --git a/visualpython/css/m_stats/regression.css b/visualpython/css/m_stats/regression.css
new file mode 100644
index 00000000..57ea4863
--- /dev/null
+++ b/visualpython/css/m_stats/regression.css
@@ -0,0 +1,17 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : regression.css
+ *    Author          : Black Logic
+ *    Note            : stylesheet for regression.html
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 05. 31
+ *    Change Date     :
+ */
+.vp-categorical-box {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    border: 0.25px solid var(--vp-border-gray-color);
+    padding: 5px;
+    min-height: 30px;
+}
\ No newline at end of file
diff --git a/visualpython/css/menuFrame.css b/visualpython/css/menuFrame.css
index 51340ae3..59f4c4b8 100644
--- a/visualpython/css/menuFrame.css
+++ b/visualpython/css/menuFrame.css
@@ -27,6 +27,19 @@
     background-color: var(--vp-background-color);
     border-bottom: 1px solid var(--vp-border-gray-color);
 }
+.vp-package-manager {
+    /* display: inline-flex; */
+    display: none;
+    float: right;
+    position: relative;
+    font-size: 18px;
+    font-weight: bold;
+    color: var(--vp-highlight-color);
+    margin: 8px 6px 8px 3px;
+    cursor: pointer;
+    width: 18px;
+    height: 18px;
+}
 .vp-version-updater {
     display: none;
     float: right;
@@ -82,14 +95,12 @@ input.vp-menu-search-box {
 .vp-menu-search-icon {
     width: 20px;
     height: 20px;
-    position: relative;
-    right: 30px;
-    top: 6px;
-    /* LAB: img to background-image */
+    position: absolute;
+    right: 45px;
+    top: 15px;
     background-image: url(../img/search.svg);
     background-repeat: no-repeat;
     background-size: contain;
-    height: 100%;
 }
 /* LAB: img to background-image */
 #vp_wrapper.lab .vp-menu-search-icon {
@@ -428,16 +439,40 @@ input.vp-menu-search-box {
 }
 /* statistics */
 .vp-menuitem.apps .stats_probDist {
-    background: top / contain no-repeat url(../img/apps/apps_white.svg);
+    background: top / contain no-repeat url(../img/apps/apps_probDist.svg);
+}
+.vp-menuitem.apps .stats_descStats {
+    background: top / contain no-repeat url(../img/apps/apps_descStats.svg);
 }
 .vp-menuitem.apps .stats_normTest {
-    background: top / contain no-repeat url(../img/apps/apps_white.svg);
+    background: top / contain no-repeat url(../img/apps/apps_probDist.svg);
 }
 .vp-menuitem.apps .stats_equalVarTest {
-    background: top / contain no-repeat url(../img/apps/apps_white.svg);
+    background: top / contain no-repeat url(../img/apps/apps_equalVarTest.svg);
+}
+.vp-menuitem.apps .stats_corrAnalysis {
+    background: top / contain no-repeat url(../img/apps/apps_corrAnalysis.svg);
+}
+.vp-menuitem.apps .stats_reliabAnalysis {
+    background: top / contain no-repeat url(../img/apps/apps_corrAnalysis.svg);
+}
+.vp-menuitem.apps .stats_chi2test {
+    background: top / contain no-repeat url(../img/apps/apps_chi2test.svg);
 }
 .vp-menuitem.apps .stats_studentstTest {
-    background: top / contain no-repeat url(../img/apps/apps_white.svg);
+    background: top / contain no-repeat url(../img/apps/apps_studentstTest.svg);
+}
+.vp-menuitem.apps .stats_anova {
+    background: top / contain no-repeat url(../img/apps/apps_anova.svg);
+}
+.vp-menuitem.apps .stats_factorAnalysis {
+    background: top / contain no-repeat url(../img/apps/apps_factorAnalysis.svg);
+}
+.vp-menuitem.apps .stats_regression {
+    background: top / contain no-repeat url(../img/apps/apps_regression.svg);
+}
+.vp-menuitem.apps .stats_logisticRegression {
+    background: top / contain no-repeat url(../img/apps/apps_logisticRegression.svg);
 }
 /* machine learning */
 .vp-menuitem.apps .ml_dataSet {
diff --git a/visualpython/css/root.css b/visualpython/css/root.css
index 94f3ec45..f7f3d6e9 100644
--- a/visualpython/css/root.css
+++ b/visualpython/css/root.css
@@ -96,6 +96,11 @@ select.vp-select:disabled {
 
 /* image icons */
 /* LAB: img to background-image */
+.vp-icon-setting {
+    background: center / contain no-repeat url(../img/setting.svg);
+    width: 100%;
+    height: 100%;
+}
 .vp-icon-refresh {
     background: top / contain no-repeat url(../img/refresh.svg);
     height: 100%;
@@ -483,7 +488,13 @@ button.vp-button {
 hr.vp-extra-menu-line {
     margin: 5px 0px 7px 0px;
 }
-/* width style*/
+/* height style */
+#vp_wrapper .h150,
+.vp-popup-frame .h150,
+.vp-inner-popup-body .h150 {
+    height: 150px !important;
+}
+/* width style */
 #vp_wrapper .wp100,
 .vp-popup-frame .wp100,
 .vp-inner-popup-body .wp100 {
@@ -564,6 +575,18 @@ hr.vp-extra-menu-line {
 .vp-inline-block {
     display: inline-block !important;
 }
+/* Flex */
+.vp-flex-box {
+    display: flex;
+}
+.vp-flex-gap5 {
+    display: flex;
+    gap: 5px;
+}
+.vp-flex-gap10 {
+    display: flex;
+    gap: 10px;
+}
 /* Grid style */
 .vp-grid-box {
     display: grid;
@@ -607,6 +630,13 @@ hr.vp-extra-menu-line {
     align-items: baseline;
     align-content: space-evenly;
 }
+.vp-grid-col-120 {
+    display: grid;
+    grid-template-columns: 120px auto;
+    grid-row-gap: 5px;
+    align-items: baseline;
+    align-content: space-evenly;
+}
 .vp-grid-col-130 {
     display: grid;
     grid-template-columns: 130px auto;
@@ -614,6 +644,14 @@ hr.vp-extra-menu-line {
     align-items: baseline;
     align-content: space-evenly;
 }
+.vp-grid-col-160 {
+    display: grid;
+    grid-template-columns: 160px auto;
+    grid-row-gap: 5px;
+    /* align-items: baseline; */
+    align-items: start;
+    align-content: space-evenly;
+}
 /* Table style */
 .vp-tbl-gap5 {
     border-spacing: 5px;
diff --git a/visualpython/data/libraries.json b/visualpython/data/libraries.json
index 8a739815..fc2cda53 100644
--- a/visualpython/data/libraries.json
+++ b/visualpython/data/libraries.json
@@ -3206,6 +3206,186 @@
                     }
                 ]
             },
+            {
+                "id"   : "pkg_statistics",
+                "type" : "package",
+                "level": 0,
+                "name" : "Statistics",
+                "path" : "visualpython - statistics",
+                "desc" : "Statistics modules",
+                "open" : true,
+                "grid" : true,
+                "item" : [
+                    {
+                        "id"   : "stats_probDist",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Prob. Distribution",
+                        "tag"  : "PROBABILITY DISTRIBUTION,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - probabilitiy distribution",
+                        "desc" : "Probability distribution",
+                        "file" : "m_stats/ProbDist",
+                        "apps" : {
+                            "color": 15,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_descStats",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Descriptive Statistics",
+                        "tag"  : "DESCRIPTIVE STATISTICS,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - descriptive statistics",
+                        "desc" : "Descriptive statistics",
+                        "file" : "m_stats/DescStats",
+                        "apps" : {
+                            "color": 15,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_normTest",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Normality test",
+                        "tag"  : "NORMALITY TEST,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - normality test",
+                        "desc" : "Normality test",
+                        "file" : "m_stats/NormTest",
+                        "apps" : {
+                            "color": 15,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_equalVarTest",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Equal Var. test",
+                        "tag"  : "EQUAL VARIANCE TEST,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - equal variance test",
+                        "desc" : "Equal Variance test",
+                        "file" : "m_stats/EqualVarTest",
+                        "apps" : {
+                            "color": 15,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_corrAnalysis",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Correlation Analysis",
+                        "tag"  : "CORRELATION ANALYSIS,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - correlation analysis",
+                        "desc" : "Correlation analysis",
+                        "file" : "m_stats/CorrAnalysis",
+                        "apps" : {
+                            "color": 16,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_reliabAnalysis",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Reliability Analysis",
+                        "tag"  : "RELIABILITY ANALYSIS,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - reliability analysis",
+                        "desc" : "Reliability analysis",
+                        "file" : "m_stats/ReliabAnalysis",
+                        "apps" : {
+                            "color": 16,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_chi2test",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Chi-square test",
+                        "tag"  : "CHI-SQUARE TEST,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - chisquare test",
+                        "desc" : "Chi-square test of independence",
+                        "file" : "m_stats/Chi2test",
+                        "apps" : {
+                            "color": 16,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_studentstTest",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Student's t-test",
+                        "tag"  : "STUDENTS T-TEST,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - students ttest",
+                        "desc" : "Student's t-test",
+                        "file" : "m_stats/StudentstTest",
+                        "apps" : {
+                            "color": 16,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_anova",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "ANOVA",
+                        "tag"  : "ANOVA,ONE-WAY ANOVA,TWO-WAY ANOVA,ANCOVA,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - anova",
+                        "desc" : "ANOVA",
+                        "file" : "m_stats/Anova",
+                        "apps" : {
+                            "color": 17,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_factorAnalysis",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Factor Analysis",
+                        "tag"  : "FACTOR ANALYSIS,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - factor analysis",
+                        "desc" : "Factor analysis",
+                        "file" : "m_stats/FactorAnalysis",
+                        "apps" : {
+                            "color": 17,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_regression",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Regression",
+                        "tag"  : "REGRESSION,HIERARCHICAL LINEAR REGRESSION,MODERATED LINEAR REGRESSION,MEDIATED LINEAR REGRESSION,DUMMY VARIABLE LINEAR REGRESSION,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - regression",
+                        "desc" : "Regression",
+                        "file" : "m_stats/Regression",
+                        "apps" : {
+                            "color": 17,
+                            "icon": "apps/apps.svg"
+                        }
+                    },
+                    {
+                        "id"   : "stats_logisticRegression",
+                        "type" : "function",
+                        "level": 1,
+                        "name" : "Logistic Regression",
+                        "tag"  : "LOGISTIC REGRESSION,STATS,STATISTICS",
+                        "path" : "visualpython - statistics - logistic regression",
+                        "desc" : "Logistic regression",
+                        "file" : "m_stats/LogisticRegression",
+                        "apps" : {
+                            "color": 17,
+                            "icon": "apps/apps.svg"
+                        }
+                    }
+                ]
+            },
             {
                 "id"   : "pkg_ml",
                 "type" : "package",
diff --git a/visualpython/data/m_library/pandasLibrary.js b/visualpython/data/m_library/pandasLibrary.js
index fce1c671..488f9ef5 100644
--- a/visualpython/data/m_library/pandasLibrary.js
+++ b/visualpython/data/m_library/pandasLibrary.js
@@ -158,7 +158,7 @@ define([
           }
         ]
       },
-      "pd004": {
+      "pd_readCsv": {
         "name": "Read CSV",
         "library": "pandas",
         "description": "",
@@ -245,7 +245,7 @@ define([
           }
         ]
       },
-      "pd005": {
+      "pd_toCsv": {
         "name": "To CSV",
         "library": "pandas",
         "description": "dataframe to csv",
@@ -3505,7 +3505,7 @@ define([
           }
         ]
       },
-      "pd076": {
+      "pd_readJson": {
         "name": "Read Json",
         "library": "pandas",
         "description": "json to pandas object",
@@ -3585,7 +3585,7 @@ define([
           }
         ]
       },
-      "pd077": {
+      "pd_toJson": {
         "name": "To Json",
         "library": "pandas",
         "description": "DataFrame/Series to Json file",
@@ -3636,7 +3636,7 @@ define([
           }
         ]
       },
-      "pd078": {
+      "pd_toPickle": {
         "name": "To Pickle",
         "library": "pandas",
         "description": "DataFrame/Series to Pickle file",
@@ -3656,13 +3656,13 @@ define([
           },
           {
             "name": "path",
-            "label": "file path/variable",
+            "label": "File path/variable",
             "required": true,
             "type": "text"
           }
         ]
       },
-      "pd079": {
+      "pd_readPickle": {
         "name": "Read Pickle",
         "library": "pandas",
         "description": "Pickle to pandas object",
@@ -3670,7 +3670,7 @@ define([
         "options": [
           {
             "name": "i0",
-            "label": "file path/object",
+            "label": "File path/object",
             "required": true,
             "type": "text",
             "component": [
@@ -6515,7 +6515,7 @@ define([
           }
         ]
       },
-      "pd123": {
+      "pd_readExcel": {
         "name": "Read Excel",
         "library": "pandas",
         "description": "excel to pandas object",
@@ -6547,11 +6547,12 @@ define([
           {
             "name": "index_col",
             "label": "Column To Use As Index",
+            "type": "text",
             "usePair": true
           },
         ]
       },
-      "pd124": {
+      "pd_toExcel": {
         "name": "To Excel",
         "library": [
           "pandas",
@@ -6699,7 +6700,91 @@ define([
             ]
           }
         ]
-      }
+      },
+      "pd_readSas": {
+        "name": "Read Sas",
+        "library": "pandas",
+        "description": "Read SAS files stored as either XPORT or SAS7BDAT format files.",
+        "code": "${o0} = pd.read_sas(${i0}${format}${encoding}${etc})",
+        "options": [
+          {
+            "name": "i0",
+            "label": "File Path",
+            "required": true,
+            "type": "text",
+            "component": [
+              "file"
+            ]
+          },
+          {
+            "name": "o0",
+            "label": "Allocate to",
+            "output": true,
+            "component": [
+              "data_select"
+            ]
+          },
+          {
+            "name": "format",
+            "label": "Format",
+            "type": "text",
+            "component": [
+              "option_select"
+            ],
+            "options": [
+              "",
+              "xport",
+              "sas7bdat"
+            ],
+            "usePair": true
+          },
+          {
+            "name": "encoding",
+            "label": "Encoding",
+            "type": "text",
+            "usePair": true
+          }
+        ]
+      },
+      "pd_readSpss": {
+        "name": "Read Spss",
+        "library": "pandas",
+        "description": "Load an SPSS file from the file path, returning a DataFrame.",
+        "code": "${o0} = pd.read_spss(${i0}${usecols}${convert_categoricals}${etc})",
+        "options": [
+          {
+            "name": "i0",
+            "label": "File Path",
+            "required": true,
+            "type": "text",
+            "component": [
+              "file"
+            ]
+          },
+          {
+            "name": "o0",
+            "label": "Allocate to",
+            "output": true,
+            "component": [
+              "data_select"
+            ]
+          },
+          {
+            "name": "usecols",
+            "label": "Use columns",
+            "usePair": true
+          },
+          {
+            "name": "convert_categoricals",
+            "label": "Convert categoricals",
+            "component": [
+              "bool_select"
+            ],
+            "default": true,
+            "usePair": true
+          },
+        ]
+      },
     }
 
     return {
diff --git a/visualpython/data/m_stats/statsLibrary.js b/visualpython/data/m_stats/statsLibrary.js
new file mode 100644
index 00000000..e43e6695
--- /dev/null
+++ b/visualpython/data/m_stats/statsLibrary.js
@@ -0,0 +1,143 @@
+define([
+], function () {
+    /**
+     * name
+     * library
+     * description
+     * code
+     * options: [
+     *      {
+     *          name
+     *          label
+     *          [optional]
+     *          component : 
+     *              - 1darr / 2darr / ndarr / scalar / param / dtype / tabblock
+     *          default
+     *          required
+     *          usePair
+     *          code
+     *      }
+     * ]
+     */
+    var STATS_LIBRARIES = {
+        /** Discrete prob. dist. */
+        'bernoulli': {
+            name: 'Bernoulli',
+            import: 'from scipy import stats',
+            code: '_rv = stats.bernoulli(${p})',
+            description: 'A Bernoulli discrete random variable.',
+            options: [
+                { name: 'p', component: ['input_number'], value: 0.6, required: true, usePair: true },
+            ]
+        },
+        'binomial': {
+            name: 'Binomial',
+            import: 'from scipy import stats',
+            code: '_rv = stats.binom(${n}${p})',
+            description: 'A binomial discrete random variable.',
+            options: [
+                { name: 'n', component: ['input_number'], value: 10, required: true, usePair: true },
+                { name: 'p', component: ['input_number'], value: 0.6, required: true, usePair: true },
+            ]
+        },
+        'multinomial': {
+            name: 'Multinomial',
+            import: 'from scipy import stats',
+            code: '_rv = stats.multinomial(${n}${p})',
+            description: 'A multinomial random variable.',
+            options: [
+                { name: 'n', component: ['input_number'], value: 10, required: true, usePair: true },
+                { name: 'p', component: ['data_select'], value: '[0.4, 0.6]', required: true, usePair: true },
+            ]
+        },
+        /** Continumous prob. dist. */
+        'uniform': {
+            name: 'Uniform',
+            import: 'from scipy import stats',
+            code: '_rv = stats.uniform()',
+            description: 'A uniform continuous random variable.',
+            options: [
+            ]
+        },
+        'normal': {
+            name: 'Normal',
+            import: 'from scipy import stats',
+            code: '_rv = stats.norm(${loc}${scale})',
+            description: 'A normal continuous random variable.',
+            options: [
+                { name: 'loc', component: ['input_number'], value: 0, usePair: true },
+                { name: 'scale', component: ['input_number'], value: 1, usePair: true },
+            ]
+        },
+        'beta': {
+            name: 'Beta',
+            import: 'from scipy import stats',
+            code: '_rv = stats.beta(${a}${b})',
+            description: 'A beta continuous random variable.',
+            options: [
+                { name: 'a', component: ['input_number'], required: true, usePair: true },
+                { name: 'b', component: ['input_number'], required: true, usePair: true },
+            ]
+        },
+        'gamma': {
+            name: 'Gamma',
+            import: 'from scipy import stats',
+            code: '_rv = stats.gamma(${a})',
+            description: 'A gamma continuous random variable.',
+            options: [
+                { name: 'a', component: ['input_number'], required: true, usePair: true },
+            ]
+        },
+        'studentst': {
+            name: "Student's t",
+            import: 'from scipy import stats',
+            code: '_rv = stats.t(${df})',
+            description: "A Student's t continuous random variable.",
+            options: [
+                { name: 'df', component: ['input_number'], required: true, usePair: true },
+            ]
+        },
+        'chi2': {
+            name: 'Chi2',
+            import: 'from scipy import stats',
+            code: '_rv = stats.chi2(${df})',
+            description: 'A chi-squared continuous random variable.',
+            options: [
+                { name: 'df', component: ['input_number'], required: true, usePair: true },
+            ]
+        },
+        'f': {
+            name: 'F',
+            import: 'from scipy import stats',
+            code: '_rv = stats.f(${dfn}${dfd})',
+            description: 'An F continuous random variable.',
+            options: [
+                { name: 'dfn', component: ['input_number'], required: true, usePair: true },
+                { name: 'dfd', component: ['input_number'], required: true, usePair: true },
+            ]
+        },
+        'dirichlet': {
+            name: 'Dirichlet',
+            import: 'from scipy import stats',
+            code: '_rv = stats.dirichlet(${alpha}${seed})',
+            description: 'A Dirichlet random variable.',
+            options: [
+                { name: 'alpha', component: ['input_number'], required: true, usePair: true },
+                { name: 'seed', component: ['input_number'], usePair: true },
+            ]
+        },
+        'multivariate_normal': {
+            name: 'Multivariate normal',
+            import: 'from scipy import stats',
+            code: '_rv = stats.multivariate_normal(${mean}${cov}${allow_singular})',
+            description: 'A multivariate normal random variable.',
+            options: [
+                { name: 'mean', component: ['data_select'], value: '[0]', usePair: true },
+                { name: 'cov', component: ['data_select'], value: '[1]', usePair: true },
+                { name: 'allow_singular', component: ['bool_select'], default: 'False', usePair: true },
+            ]
+        },
+    }
+
+    return STATS_LIBRARIES;
+});
\ No newline at end of file
diff --git a/visualpython/data/sample_csv/iris.csv b/visualpython/data/sample_csv/iris.csv
index 1b9d0294..79ac3614 100644
--- a/visualpython/data/sample_csv/iris.csv
+++ b/visualpython/data/sample_csv/iris.csv
@@ -1,4 +1,4 @@
-"sepal.length","sepal.width","petal.length","petal.width","variety"
+"sepal_length","sepal_width","petal_length","petal_width","variety"
 5.1,3.5,1.4,.2,"Setosa"
 4.9,3,1.4,.2,"Setosa"
 4.7,3.2,1.3,.2,"Setosa"
diff --git a/visualpython/html/m_apps/file.html b/visualpython/html/m_apps/file.html
index c9d92782..a72000de 100644
--- a/visualpython/html/m_apps/file.html
+++ b/visualpython/html/m_apps/file.html
@@ -60,7 +60,7 @@
                     </thead>
                     <tbody>
                         <tr>
-                            <td><label for="vp_sampleFile" class="vp-orange-text">Sample Data</label></td>
+                            <td><label for="vp_sampleFile" class="vp-bold vp-orange-text">Sample Data</label></td>
                             <td>
                                 <select class="vp-select option-select" id="vp_sampleFile">
                                     <option name="vp_sampleFile" value="iris.csv">iris</option>
diff --git a/visualpython/html/m_stats/anova.html b/visualpython/html/m_stats/anova.html
new file mode 100644
index 00000000..15cc0f06
--- /dev/null
+++ b/visualpython/html/m_stats/anova.html
@@ -0,0 +1,74 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : anova.html
+    Author          : Black Logic
+    Note            : ANOVA
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 24
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <select id="testType" class="vp-big-select vp-state">
+            <option value="one-way">One-way ANOVA</option>
+            <option value="two-way">Two-way ANOVA</option>
+            <option value="ancova">ANCOVA - Analysis of covariance</option>
+        </select>
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-box vp-st-option-box">
+                <div class="vp-grid-col-130">
+                    <label for="data" class="vp-orange-text">Data</label>
+                    <div class="vp-flex-gap5">
+                        <input type="text" id="data" class="vp-state vp-input" required/>
+                    </div>
+                    <label for="depVar" class="vp-orange-text">Dependent variable</label>
+                    <select id="depVar" class="vp-state vp-select" required>
+                    </select>
+                </div>
+                <div class="vp-st-option one-way ancova vp-grid-col-130">
+                    <label for="factor" class="vp-orange-text">Factor</label>
+                    <select id="factor" class="vp-state vp-select" required>
+                    </select>
+                </div>
+                <div class="vp-st-option two-way vp-grid-col-130">
+                    <label for="factorA" class="vp-orange-text">Factor A</label>
+                    <select id="factorA" class="vp-state vp-select" required>
+                    </select>
+                    <label for="factorB" class="vp-orange-text">Factor B</label>
+                    <select id="factorB" class="vp-state vp-select" required>
+                    </select>
+                </div>
+                <div class="vp-st-option ancova vp-grid-col-130">
+                    <label for="covariate" class="vp-orange-text">Covariate</label>
+                    <select id="covariate" class="vp-state vp-select" required>
+                    </select>
+                </div>
+                <div class="vp-st-option one-way two-way">
+                    <hr style="margin: 5px 0;"/>
+                </div>
+                <div class="vp-st-option one-way two-way vp-grid-col-130">
+                    <label for="sigLevel" class="vp-orange-text">Significance level</label>
+                    <input type="number" id="sigLevel" class="vp-state vp-input" required/>
+                    <label for="covariate" class="vp-orange-text">Post hoc analysis</label>
+                    <div class="vp-grid-col-p50">
+                        <label><input type="checkbox" id="tukeyHSD" class="vp-state" checked><span>Tukey HSD</span></label>
+                        <label><input type="checkbox" id="tukey" class="vp-state"><span>Tukey</span></label>
+                        <label><input type="checkbox" id="scheffe" class="vp-state"><span>Scheffe</span></label>
+                        <label><input type="checkbox" id="duncan" class="vp-state"><span>Duncan</span></label>
+                        <label><input type="checkbox" id="bonferroni" class="vp-state"><span>Bonferroni</span></label>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="vp-grid-border-box">
+            <label for="statistics">Display</label>
+            <div class="vp-grid-box">
+                <label><input type="checkbox" id="statistics" class="vp-state" checked><span>Statistics</span></label>
+                <label><input type="checkbox" id="boxplot" class="vp-state" checked><span>Boxplot</span></label>
+                <label class="vp-st-option one-way two-way"><input type="checkbox" id="equalVariance" class="vp-state" checked><span>Equal Variance</span></label>
+                <label class="vp-st-option two-way"><input type="checkbox" id="interPlot" class="vp-state" checked><span>Interaction plot</span></label>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/m_stats/corrAnalysis.html b/visualpython/html/m_stats/corrAnalysis.html
new file mode 100644
index 00000000..6f6ec244
--- /dev/null
+++ b/visualpython/html/m_stats/corrAnalysis.html
@@ -0,0 +1,39 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : corrAnalysis.html
+    Author          : Black Logic
+    Note            : Correlation Analylsis
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 24
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-box vp-grid-col-160">
+                <label for="data" class="vp-orange-text">Data</label>
+                <div class="vp-flex-gap5">
+                    <input type="text" id="data" class="vp-state vp-input" required/>
+                </div>
+                <label>Variable</label>
+                <div id="variable" class="h150"></div>
+                <label for="corrType" class="vp-orange-text">Correlation</label>
+                <select id="corrType" class="vp-select vp-state">
+                    <option value="pearson" selected>Pearson</option>
+                    <option value="spearman">Spearman</option>
+                    <option value="kendall">Kendall's Tau</option>
+                </select>
+            </div>
+        </div>
+        <div class="vp-grid-border-box">
+            <label for="corrAnlaysis">Display</label>
+            <div class="vp-grid-box">
+                <div><label><input type="checkbox" id="corrAnlaysis" class="vp-state" checked><span>Correlation Analysis</span></label></div>
+                <div><label><input type="checkbox" id="corrMatrix" class="vp-state" checked><span>Correlation matrix</span></label></div>
+                <div><label><input type="checkbox" id="corrHeatmap" class="vp-state"><span>Correlation heatmap</span></label></div>
+                <div><label><input type="checkbox" id="scatterMatrix" class="vp-state"><span>Scatterplot matrix</span></label></div>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/m_stats/descStats.html b/visualpython/html/m_stats/descStats.html
new file mode 100644
index 00000000..d6ae0475
--- /dev/null
+++ b/visualpython/html/m_stats/descStats.html
@@ -0,0 +1,99 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : descStats.html
+    Author          : Black Logic
+    Note            : Descriptive Statistics
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 31
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-box vp-grid-col-130">
+                <label for="data" class="vp-orange-text">Data</label>
+                <div class="vp-flex-gap5">
+                    <input type="text" id="data" class="vp-state vp-input" required/>
+                </div>
+                <label>Variable</label>
+                <div id="variable" class="h150"></div>
+            </div>
+        </div>
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-col-130">
+                <label>Central tendency</label>
+                <div class="vp-flex-gap10">
+                    <label><input type="checkbox" id="mean" class="vp-state" checked><span>Mean</span></label>
+                    <label><input type="checkbox" id="median" class="vp-state"><span>Median</span></label>
+                    <label><input type="checkbox" id="mode" class="vp-state"><span>Mode</span></label>
+                    <label><input type="checkbox" id="sum" class="vp-state" checked><span>Sum</span></label>
+                </div>
+            </div>
+            <hr style="margin: 5px 0;"/>
+            <div class="vp-grid-col-130">
+                <label>Dispersion</label>
+                <div class="vp-grid-box">
+                    <div class="vp-flex-gap10">
+                        <label><input type="checkbox" id="min" class="vp-state"><span>Minimum</span></label>
+                        <label><input type="checkbox" id="max" class="vp-state"><span>Maximum</span></label>
+                        <label><input type="checkbox" id="range" class="vp-state"><span>Range</span></label>
+                    </div>
+                    <div class="vp-flex-gap10">
+                        <label><input type="checkbox" id="std" class="vp-state" checked><span>Standard deviation(Std. deviation)</span></label>
+                        <label><input type="checkbox" id="variance" class="vp-state" checked><span>Variance</span></label>
+                    </div>
+                    <label><input type="checkbox" id="semean" class="vp-state"><span>Standard Error of the Mean (S.E. mean)</span></label>
+                </div>
+            </div>
+            <hr style="margin: 5px 0;"/>
+            <div class="vp-grid-col-130">
+                <label>Distribution</label>
+                <div class="vp-flex-gap10">
+                    <label><input type="checkbox" id="skew" class="vp-state"><span>Skewness</span></label>
+                    <label><input type="checkbox" id="kurtosis" class="vp-state"><span>Kurtosis</span></label>
+                </div>
+            </div>
+            <hr style="margin: 5px 0;"/>
+            <div class="vp-grid-col-130">
+                <label>Percentile values</label>
+                <div class="vp-grid-box">
+                    <label><input type="checkbox" id="quantile" class="vp-state" checked><span>Quantile</span></label>
+                    <div class="vp-grid-box">
+                        <div class="vp-flex-gap5">
+                            <label><input type="checkbox" id="usePercentile" class="vp-state"><span>Percentile</span></label>
+                            <input id="percentile" type="number" class="vp-input m vp-state" min="0" max="100" step="5" disabled/><button id="addPercentile" class="vp-button" disabled>Add</button>
+                        </div>
+                        <div class="vp-percentile-box vp-scrollbar disabled">
+                            
+                        </div>
+                    </div>
+                </div>    
+            </div>
+        </div>
+        <div class="vp-grid-border-box">
+            <label>Frequency table</label>
+            <div class="vp-grid-box">
+                <div class="vp-flex-gap10">
+                    <label><input type="checkbox" id="frequency" class="vp-state" checked><span>Frequency</span></label>
+                    <label><input type="checkbox" id="percent" class="vp-state" checked><span>Percent</span></label>
+                    <label><input type="checkbox" id="validPercent" class="vp-state" checked><span>Valid percent</span></label>
+                    <label><input type="checkbox" id="cumulativePercent" class="vp-state" checked><span>Cumulative percent</span></label>
+                </div>
+                <hr style="margin: 5px 0;"/>
+                <div class="vp-flex-gap10">
+                    <label>Number of unique values</label>
+                    <input id="noUniqVals" type="number" class="vp-input m vp-state" value="10"/>
+                </div>
+            </div>
+        </div>
+        <div class="vp-grid-border-box">
+            <label>Display</label>
+            <div class="vp-grid-box">
+                <div><label><input type="checkbox" id="histogram" class="vp-state" checked><span>Histogram</span></label></div>
+                <div><label><input type="checkbox" id="scatterMatrix" class="vp-state" checked><span>Scatter matrix</span></label></div>
+                <div><label><input type="checkbox" id="boxplot" class="vp-state" checked><span>Boxplot</span></label></div>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/m_stats/equalVarTest.html b/visualpython/html/m_stats/equalVarTest.html
new file mode 100644
index 00000000..305c9dde
--- /dev/null
+++ b/visualpython/html/m_stats/equalVarTest.html
@@ -0,0 +1,62 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : equalVarTest.html
+    Author          : Black Logic
+    Note            : equal variance test
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 16
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <select id="testType" class="vp-big-select vp-state">
+            <option value="bartlett">Bartlett test</option>
+            <option value="levene">Levene test</option>
+            <option value="fligner">Fligner test</option>
+        </select>
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-box">
+                <div class="vp-grid-col-160">
+                    <label class="vp-orange-text">Input type</label>
+                    <div>
+                        <label><input type="radio" name="inputType" class="vp-state" value="long-data" checked><span>Long data</span></label>
+                        <label><input type="radio" name="inputType" class="vp-state" value="wide-data"><span>Wide data</span></label>
+                    </div>
+                    <label for="data" class="vp-orange-text">Data</label>
+                    <div class="vp-flex-gap5">
+                        <input type="text" id="data" class="vp-state vp-input" required/>
+                    </div>
+                </div>
+                <div class="vp-variable-box long-data vp-grid-col-160">
+                    <label></label>
+                    <div class="vp-grid-col-95">
+                        <label for="variable" class="vp-orange-text">Variable</label>
+                        <select id="variable" class="vp-state vp-select" required></select>
+                        <label for="factor" class="vp-orange-text">Factor</label>
+                        <select id="factor" class="vp-state vp-select" required></select>
+                    </div>
+                </div>
+                <div class="vp-variable-box wide-data vp-grid-col-160">
+                    <label></label>
+                    <div id="variableMulti" class="h150"></div>
+                </div>
+                
+                <div class="vp-st-option levene fligner vp-grid-col-160">
+                    <label for="center" class="vp-orange-text">Center</label>
+                    <select id="center" class="vp-select vp-state">
+                        <option value="mean" selected>mean</option>
+                        <option value="median">median</option>
+                        <option value="trimmed">trimmed</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+        <div class="vp-grid-border-box">
+            <label for="histogram">Display</label>
+            <div class="vp-grid-box">
+                <label><input type="checkbox" id="histogram" class="vp-state"><span>Histogram</span></label>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/m_stats/factorAnalysis.html b/visualpython/html/m_stats/factorAnalysis.html
new file mode 100644
index 00000000..fa5197a2
--- /dev/null
+++ b/visualpython/html/m_stats/factorAnalysis.html
@@ -0,0 +1,51 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : factorAnalysis.html
+    Author          : Black Logic
+    Note            : Factor Analylsis
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 24
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-box vp-grid-col-160">
+                <label for="data" class="vp-orange-text">Data</label>
+                <div class="vp-flex-gap5">
+                    <input type="text" id="data" class="vp-state vp-input" required/>
+                </div>
+                <label>Variable</label>
+                <div id="variable" class="h150"></div>
+                <label for="rotation">Rotation</label>
+                <select id="rotation" class="vp-select vp-state">
+                </select>
+                <label for="method">Method</label>
+                <select id="method" class="vp-select vp-state">
+                </select>
+                <label for="impute">Impute</label>
+                <select id="impute" class="vp-select vp-state">
+                </select>
+                <label for="extract">Extract</label>
+                <div class="vp-grid-box">
+                    <div class="vp-grid-col-p50">
+                        <label><input type="radio" name="extract" value="eigenvalue" checked/><span>Based on eigenvalue</span></label>
+                        <input type="number" id="eigenvalue" class="vp-input vp-state" value="1">
+                    </div>
+                    <div class="vp-grid-col-p50">
+                        <label><input type="radio" name="extract" value="factor"/><span>Number of factor</span></label>
+                        <input type="number" id="factor" class="vp-input vp-state">
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="vp-grid-border-box">
+            <label for="corrMatrix">Display</label>
+            <div class="vp-grid-box">
+                <div><label><input type="checkbox" id="corrMatrix" class="vp-state" checked><span>Correlation matrix</span></label></div>
+                <div><label><input type="checkbox" id="screePlot" class="vp-state" checked><span>Scree plot</span></label></div>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/m_stats/logisticRegression.html b/visualpython/html/m_stats/logisticRegression.html
new file mode 100644
index 00000000..77af0882
--- /dev/null
+++ b/visualpython/html/m_stats/logisticRegression.html
@@ -0,0 +1,38 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : corrAnalysis.html
+    Author          : Black Logic
+    Note            : Correlation Analylsis
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 24
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-col-160">
+                <label for="data" class="vp-orange-text">Data</label>
+                <div class="vp-flex-gap5">
+                    <input type="text" id="data" class="vp-state vp-input" required/>
+                </div>
+            </div>
+            <div class="vp-grid-col-160">
+                <label for="dependent" class="vp-orange-text">Dependent variable</label>
+                <div>
+                    <select id="dependent" class="vp-select vp-state" required></select>
+                    <label><input type="checkbox" id="encoding" class="vp-state" checked><span>Encoding labels</span></label>
+                </div>
+                <label></label>
+                <div>
+                    <label class="vp-italic vp-orange-text">NOTE: </label>
+                    <label class="vp-italic vp-gray-text">Dependent variables must have two categories.</label>
+                </div>
+                <label class="vp-orange-text">Independent variable</label>
+                <div id="independent" class="h150"></div>
+            </div>
+            <div><label><input type="checkbox" id="showOdds" class="vp-state" checked><span>Show Odds</span></label></div>
+            <div><label><input type="checkbox" id="multiCollinearity" class="vp-state" checked><span>Multi-collinearity statistics</span></label></div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/m_stats/normTest.html b/visualpython/html/m_stats/normTest.html
new file mode 100644
index 00000000..3685a4aa
--- /dev/null
+++ b/visualpython/html/m_stats/normTest.html
@@ -0,0 +1,50 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : normTest.html
+    Author          : Black Logic
+    Note            : normality test
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 16
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <select id="testType" class="vp-big-select vp-state">
+            <option value="shapiro-wilk">Shapiro-Wilk test</option>
+            <option value="anderson-darling">Anderson-Darling test</option>
+            <option value="kolmogorov-smirnov">Kolmogorov-Smirnov test</option>
+            <option value="dagostino-pearson">D'Agostino and Pearson's test</option>
+            <option value="jarque-bera">Jarque-Bera test</option>
+        </select>
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-box vp-st-option-box">
+                <div class="vp-grid-col-160">
+                    <label for="data0" class="vp-orange-text">Data</label>
+                    <div class="vp-flex-gap5">
+                        <input type="text" id="data0" class="vp-state vp-input" required/>
+                    </div>
+                    <label></label>
+                    <select id="var0" class="vp-state vp-select" required>
+                    </select>
+                </div>
+                <div class="vp-st-option kolmogorov-smirnov vp-grid-col-160">
+                    <label for="alterHypo" class="vp-orange-text">Alternative hypothesis</label>
+                    <select id="alterHypo" class="vp-select vp-state" required>
+                        <option value="two-sided" selected>two-sided</option>
+                        <option value="less">less</option>
+                        <option value="greater">greater</option>
+                    </select>
+                </div>
+            </div>
+        </div>
+        <div class="vp-grid-border-box">
+            <label for="histogram">Display</label>
+            <div class="vp-grid-box">
+                <label><input type="checkbox" id="histogram" class="vp-state"><span>Histogram</span></label>
+                <label><input type="checkbox" id="boxplot" class="vp-state"><span>Boxplot</span></label>
+                <label><input type="checkbox" id="qqplot" class="vp-state" checked><span>Q-Q Plot</span></label>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/m_stats/probDist.html b/visualpython/html/m_stats/probDist.html
new file mode 100644
index 00000000..0bab3ef5
--- /dev/null
+++ b/visualpython/html/m_stats/probDist.html
@@ -0,0 +1,65 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : propDist.html
+    Author          : Black Logic
+    Note            : probability distribution
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 16
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <select id="distType" class="vp-big-select vp-state">
+        </select>
+        <div class="vp-grid-border-box">
+            <div class="vp-pd-dist-option-box vp-grid-col-160">
+
+            </div>
+            <hr style="margin: 5px 0;"/>
+            <div class="vp-grid-col-160">
+                <label for="action" class="vp-orange-text">Action</label>
+                <select id="action" class="vp-select l">
+                    <option value="random-number">Generate random numbers</option>
+                    <option value="distribution-plot">Show distribution plot</option>
+                    <option class="vp-pd-display-option cont" value="stats-to-pvalue">Statistics to P-value</option>
+                    <option class="vp-pd-display-option cont" value="pvalue-to-stats">P-value to Statistics</option>
+                </select>
+            </div>
+            <div class="vp-grid-border-box vp-pd-action-box random-number">
+                <div class="vp-grid-col-160">
+                    <label for="size" class="vp-orange-text">Size</label>
+                    <input type="number" class="vp-input vp-state" id="size" value="10000" required/>
+                    <label for="randomState">Random state</label>
+                    <input type="number" class="vp-input vp-state" id="randomState"/>
+                    <label for="allocateTo" class="vp-orange-text">Allocate to</label>
+                    <input type="text" class="vp-input vp-state" id="allocateTo" value="samples" required/>
+                </div>
+                <label><input type="checkbox" id="sampledDist" class="vp-state" checked><span>Show sampled distribution</span></label>
+            </div>
+            <div class="vp-grid-border-box vp-pd-action-box distribution-plot" style="display:none;">
+                <label class="vp-pd-display-option dist"><input type="checkbox" id="probMassFunc" class="vp-state"><span>Probability mass function</span></label>
+                <label class="vp-pd-display-option cont"><input type="checkbox" id="probDensityFunc" class="vp-state"><span>Probability density function</span></label>
+                <label class="vp-pd-display-option cont"><input type="checkbox" id="cumDistFunc" class="vp-state"><span>Cumulative distribution function</span></label>
+            </div>
+            <div class="vp-grid-border-box vp-grid-col-160 vp-pd-action-box stats-to-pvalue" style="display:none;">
+                <label for="stats" class="vp-orange-text">Statistic</label>
+                <input type="number" class="vp-input vp-state" id="stats" required/>
+                <label for="pAlter">Alternative</label>
+                <select id="pAlter" class="vp-select vp-state">
+                    <option value="one-sided">One-sided</option>
+                    <option value="two-sided" selected>Two-sided</option>
+                </select>
+            </div>
+            <div class="vp-grid-border-box vp-grid-col-160 vp-pd-action-box pvalue-to-stats" style="display:none;">
+                <label for="pvalue" class="vp-orange-text">Proportional value</label>
+                <input type="number" class="vp-input vp-state" id="pvalue" required/>
+                <label for="statsAlter">Alternative</label>
+                <select id="statsAlter" class="vp-select vp-state">
+                    <option value="one-sided">One-sided</option>
+                    <option value="two-sided" selected>Two-sided</option>
+                </select>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/m_stats/regression.html b/visualpython/html/m_stats/regression.html
new file mode 100644
index 00000000..124f7066
--- /dev/null
+++ b/visualpython/html/m_stats/regression.html
@@ -0,0 +1,82 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : regression.html
+    Author          : Black Logic
+    Note            : equal variance test
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 31
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <select id="testType" class="vp-big-select vp-state">
+            <option value="simple">Simple linear regression</option>
+            <option value="multiple">Multiple linear regression</option>
+            <option value="hierarchical">Hierarchical linear regression</option>
+            <option value="moderated">Moderated linear regression</option>
+            <option value="mediated">Mediated linear regression</option>
+            <option value="dummy">Dummy variable linear regression</option>
+        </select>
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-box">
+                <div class="vp-grid-col-160">
+                    <label for="data" class="vp-orange-text">Data</label>
+                    <div class="vp-flex-gap5">
+                        <input type="text" id="data" class="vp-state vp-input" required/>
+                    </div>
+                    <label for="dependent" class="vp-orange-text">Dependent variable</label>
+                    <select id="dependent" class="vp-select vp-state" required></select>
+                    <label for="independent" class="vp-orange-text">Independent variable</label>
+                    <div>
+                        <div class="vp-st-option simple moderated mediated">
+                            <select id="independent" class="vp-select vp-state" required></select>
+                        </div>
+                        <div id="independentBox" class="h150 vp-st-option multiple hierarchical dummy"></div>
+                    </div>
+                </div>
+                <div class="vp-st-option moderated vp-grid-col-160">
+                    <label for="moderated" class="vp-orange-text">Moderated variable</label>
+                    <select id="moderated" class="vp-select vp-state" required></select>
+                </div>
+                <div class="vp-st-option mediated vp-grid-col-160">
+                    <label for="mediated" class="vp-orange-text">Mediated variable</label>
+                    <select id="mediated" class="vp-select vp-state" required></select>
+                </div>
+                <div class="vp-st-option moderated">
+                    <label><input type="checkbox" id="meanCentering" class="vp-state" checked><span>Mean Centering</span></label>
+                </div>
+                <div class="vp-st-option mediated">
+                    <label><input type="checkbox" id="sobelTest" class="vp-state" checked><span>Sobel Test</span></label>
+                </div>
+                <div class="vp-st-option multiple vp-grid-col-160">
+                    <label for="method" class="vp-orange-text">Method</label>
+                    <select id="method" class="vp-select vp-state">
+                        <option value="enter" selected>Enter</option>
+                        <option value="stepwise">Stepwise</option>
+                        <option value="backward">Backward</option>
+                        <option value="forward">Forward</option>
+                    </select>
+                </div>
+                <div class="vp-st-option dummy vp-grid-col-160">
+                    <label for="method">Categorical variables</label>
+                    <div class="vp-categorical-box">
+                    </div>
+                </div>
+                <div class="vp-st-option multiple hierarchical moderated mediated dummy">
+                    <hr style="margin: 5px 0;"/>
+                    <label><input type="checkbox" id="multiCollinearity" class="vp-state" checked><span>Multi-collinearity statistics</span></label>
+                </div>
+            </div>
+        </div>
+        <div class="vp-grid-border-box">
+            <label>Residual</label>
+            <div class="vp-grid-box vp-grid-col-p50">
+                <label><input type="checkbox" id="statistics" class="vp-state"><span>Residual statistics</span></label>
+                <label><input type="checkbox" id="normTest" class="vp-state" checked><span>Residual Normality test</span></label>
+                <label><input type="checkbox" id="histogram" class="vp-state" checked><span>Residual histogram</span></label>
+                <label><input type="checkbox" id="scatterplot" class="vp-state" checked><span>Residual scatterplot</span></label>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/m_stats/reliabAnalysis.html b/visualpython/html/m_stats/reliabAnalysis.html
new file mode 100644
index 00000000..bbbb3e6e
--- /dev/null
+++ b/visualpython/html/m_stats/reliabAnalysis.html
@@ -0,0 +1,24 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : reliabAnalysis.html
+    Author          : Black Logic
+    Note            : Reliability Analylsis
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 24
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-box vp-grid-col-160">
+                <label for="data" class="vp-orange-text">Data</label>
+                <div class="vp-flex-gap5">
+                    <input type="text" id="data" class="vp-state vp-input" required/>
+                </div>
+                <label>Variable</label>
+                <div id="variable" class="h150"></div>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/m_stats/studentstTest.html b/visualpython/html/m_stats/studentstTest.html
new file mode 100644
index 00000000..c250b06d
--- /dev/null
+++ b/visualpython/html/m_stats/studentstTest.html
@@ -0,0 +1,86 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : studentstTest.html
+    Author          : Black Logic
+    Note            : student's t-test
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 16
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <select id="testType" class="vp-big-select vp-state">
+            <option value="one-sample">One-sample t-test</option>
+            <option value="two-sample">Independent two-sample t-test</option>
+            <option value="paired-sample">Paired samples t-test</option>
+        </select>
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-box vp-st-option-box">
+                <div class="vp-st-option two-sample vp-grid-col-160">
+                    <label class="vp-orange-text">Input type</label>
+                    <div>
+                        <label><input type="radio" name="inputType" class="vp-state" value="long-data" checked><span>Long data</span></label>
+                        <label><input type="radio" name="inputType" class="vp-state" value="wide-data"><span>Wide data</span></label>
+                    </div>
+                </div>
+                <div class="vp-grid-col-160">
+                    <label for="data" class="vp-orange-text">Data</label>
+                    <div class="vp-flex-gap5">
+                        <input type="text" id="data" class="vp-state vp-input" required/>
+                    </div>
+                </div>
+                <div class="vp-st-option one-sample two-sample-long-data vp-grid-col-160">
+                    <label for="testVariable" class="vp-orange-text">Test variable</label>
+                    <select id="testVariable" class="vp-select vp-state" required></select>
+                </div>
+                <div class="vp-st-option two-sample-long-data vp-grid-col-160">
+                    <label for="groupingVariable" class="vp-orange-text">Grouping variable</label>
+                    <select id="groupingVariable" class="vp-select vp-state" required></select>
+                    <label></label>
+                    <div class="vp-grid-col-95">
+                        <label for="group1">Group 1</label>
+                        <select id="group1" class="vp-select vp-state"></select>
+                        <label for="group2">Group 2</label>
+                        <select id="group2" class="vp-select vp-state"></select>
+                    </div>
+                </div>
+                <div class="vp-st-option two-sample-wide-data vp-grid-col-160">
+                    <label for="testVariable1" class="vp-orange-text">Test variable 1</label>
+                    <select id="testVariable1" class="vp-select vp-state" required></select>
+                    <label for="testVariable2" class="vp-orange-text">Test variable 2</label>
+                    <select id="testVariable2" class="vp-select vp-state" required></select>
+                </div>
+                <div class="vp-st-option paired-sample vp-grid-col-160">
+                    <label for="pairedVariable1" class="vp-orange-text">Paired variable 1</label>
+                    <select id="pairedVariable1" class="vp-select vp-state" required></select>
+                    <label for="pairedVariable2" class="vp-orange-text">Paired variable 2</label>
+                    <select id="pairedVariable2" class="vp-select vp-state" required></select>
+                </div>
+                <div class="vp-st-option one-sample vp-grid-col-160">
+                        <label for="testValue" class="vp-orange-text">Test value</label>
+                        <input type="number" id="testValue" class="vp-state vp-input" required/>
+                        <label></label>
+                        <div>
+                            <label class="vp-orange-text vp-italic">NOTE:&nbsp;</label><label class="vp-gray-text vp-italic">Expected value in null hypothesis.</label>
+                        </div>
+                </div>
+                <hr style="margin: 5px 0;"/>
+                <div class="vp-st-option one-sample two-sample paired-sample vp-grid-col-160">
+                    <label for="alterHypo" class="vp-orange-text">Alternative hypothesis</label>
+                    <select id="alterHypo" class="vp-select vp-state" required>
+                        <option value="two-sided" selected>two-sided</option>
+                        <option value="less">less</option>
+                        <option value="greater">greater</option>
+                    </select>
+                </div>
+                <div class="vp-st-option one-sample paired-sample vp-grid-col-160">
+                    <label for="confInt" class="vp-orange-text">Confidence interval</label>
+                    <div>
+                        <input type="number" id="confInt" class="vp-state vp-input" value="95" required/> %
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/html/menuFrame.html b/visualpython/html/menuFrame.html
index a46f4d37..95404dfe 100644
--- a/visualpython/html/menuFrame.html
+++ b/visualpython/html/menuFrame.html
@@ -47,7 +47,9 @@
                     </ul>
                 </div>
             </div>
-
+            <div id="vp_packageManager" class="vp-package-manager">
+                <span class="vp-icon-setting"></span>
+            </div>
             <div id="vp_versionUpdater" class="vp-version-updater">
                 <span class="fa fa-circle"></span>
             </div>
diff --git a/visualpython/img/apps/apps_anova.svg b/visualpython/img/apps/apps_anova.svg
new file mode 100644
index 00000000..6564ac04
--- /dev/null
+++ b/visualpython/img/apps/apps_anova.svg
@@ -0,0 +1,7 @@
+<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M28 0H0V28H28V0Z" fill="white" fill-opacity="0.01"/>
+<path d="M3 4V26H25" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M8 10.5H6V13V15.5H8M8 10.5H10V13V15.5H8M8 10.5V8H9.5H6.5M8 15.5V18H9.5H6.5" stroke="white" stroke-width="1.5"/>
+<path d="M15 12.6087H13V16V19.3913H15M15 12.6087H17V16V19.3913H15M15 12.6087V10H13.5H16.5M15 19.3913V22H16.5H13.5" stroke="white" stroke-width="1.5"/>
+<path d="M22 8H20V10V12H22M22 8H24V10V12H22M22 8V5H20.5H23.5M22 12V15H20.5H23.5" stroke="white" stroke-width="1.5"/>
+</svg>
diff --git a/visualpython/img/apps/apps_corrAnalysis.svg b/visualpython/img/apps/apps_corrAnalysis.svg
new file mode 100644
index 00000000..c433dd49
--- /dev/null
+++ b/visualpython/img/apps/apps_corrAnalysis.svg
@@ -0,0 +1,9 @@
+<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M18.578 11.3355H16.752C16.0194 11.3369 15.314 11.0587 14.7799 10.5577L9.52828 5.63471H9.5281C8.74346 4.89608 7.70601 4.48578 6.62839 4.48792H4.80237C4.86443 4.71029 4.89509 4.94015 4.89367 5.1709C4.8951 5.4029 4.86442 5.63402 4.80237 5.85744H6.62839C7.35861 5.85637 8.06158 6.13473 8.59314 6.63527L13.8448 11.5582L13.8449 11.5584C14.6303 12.2956 15.6674 12.7057 16.7447 12.705H18.5707C18.5124 12.481 18.4853 12.2499 18.4903 12.0185C18.4895 11.7879 18.519 11.5584 18.578 11.3355Z" fill="white"/>
+<path d="M12.8224 11.3462L11.7852 10.3783L11.7451 10.4587L10.5509 12.6901L11.588 13.6616L11.7451 13.3695L12.8224 11.3462Z" fill="white"/>
+<path d="M8.30835 16.8975C8.10203 17.2861 7.79373 17.611 7.41675 17.8374C7.03977 18.0639 6.60805 18.1834 6.1683 18.183H4.79504C4.8571 18.4054 4.88777 18.6352 4.88634 18.866C4.88777 19.098 4.8571 19.3291 4.79504 19.5525H6.1683C6.85625 19.5536 7.53156 19.3671 8.12164 19.0131C8.71153 18.6593 9.19406 18.1515 9.51717 17.5439L10.3863 15.9151L9.35274 14.9401L8.30835 16.8975Z" fill="white"/>
+<path d="M18.5778 4.48792H17.2046C16.5168 4.48775 15.8418 4.67463 15.252 5.02843C14.6621 5.38223 14.1796 5.88971 13.8557 6.49654L12.9866 8.12535L14.0237 9.09684L15.0646 7.14295C15.2709 6.75457 15.5792 6.42967 15.9562 6.20319C16.3332 5.97673 16.7649 5.85725 17.2046 5.85743H18.5814C18.5196 5.63399 18.4887 5.40289 18.4901 5.17089C18.4894 4.94032 18.5188 4.71084 18.5778 4.48792Z" fill="white"/>
+<path d="M18.578 18.1829H16.752C16.0219 18.1826 15.3195 17.9044 14.7872 17.4051L11.745 14.5748L9.52827 12.4968H9.52809C8.74345 11.7581 7.706 11.3478 6.62838 11.35H4.80236C4.86049 11.5679 4.88884 11.7928 4.88634 12.0184C4.88777 12.2503 4.8571 12.4815 4.79504 12.7049H6.62106C7.35343 12.701 8.05923 12.9781 8.59312 13.4792L13.8447 18.4021H13.8449C14.6302 19.1395 15.6674 19.5496 16.7446 19.5489H18.5707C18.5086 19.3253 18.4779 19.0942 18.4794 18.8624C18.4824 18.6325 18.5156 18.4041 18.578 18.1829Z" fill="white"/>
+<path d="M4.33861 5.17068C4.33897 4.61092 4.12302 4.07256 3.7357 3.66851C3.34839 3.26425 2.81987 3.02548 2.26065 3.00192C1.70125 2.97838 1.15454 3.17204 0.734735 3.54224C0.314788 3.91244 0.0542696 4.43064 0.00752356 4.98845C-0.0390171 5.54625 0.131638 6.10063 0.48416 6.5354C0.836705 6.97033 1.34367 7.25207 1.89914 7.32182C1.89878 7.32789 1.89878 7.33395 1.89914 7.34001V9.84901C1.89878 9.85507 1.89878 9.86113 1.89914 9.8672C1.19672 9.95903 0.583313 10.3877 0.25609 11.0159C-0.0711322 11.6442 -0.0711322 12.3924 0.25609 13.0206C0.583313 13.6489 1.19672 14.0775 1.89914 14.1694V16.6784V16.6782C1.16198 16.7707 0.523556 17.234 0.206879 17.9061C-0.109643 18.578 -0.060427 19.3653 0.337412 19.9927C0.735247 20.6198 1.4264 21 2.16932 21C2.91219 21 3.60338 20.6198 4.00123 19.9927C4.39906 19.3653 4.44828 18.578 4.13176 17.9061C3.81506 17.234 3.17666 16.7707 2.43968 16.6782V14.1876C3.14192 14.0959 3.75533 13.6671 4.08255 13.0388C4.40977 12.4108 4.40977 11.6624 4.08255 11.0341C3.75533 10.4061 3.14192 9.97722 2.43968 9.88555C2.44004 9.87949 2.44004 9.87325 2.43968 9.86718V7.34002C2.44004 7.33396 2.44004 7.32789 2.43968 7.32183C2.96375 7.25603 3.44595 7.00121 3.79563 6.60516C4.14514 6.20911 4.33828 5.69909 4.33865 5.17074L4.33861 5.17068Z" fill="white"/>
+<path d="M23.373 12.0183C23.3726 11.4899 23.1795 10.9799 22.8299 10.5839C22.4803 10.1878 21.9981 9.93297 21.4739 9.86719C21.4743 9.86113 21.4743 9.85507 21.4739 9.849V7.34001C21.4743 7.33395 21.4743 7.32788 21.4739 7.32182C22.2111 7.22927 22.8495 6.76598 23.1662 6.09405C23.4827 5.42194 23.4335 4.63465 23.0356 4.00751C22.6378 3.38018 21.9466 3 21.2037 3C20.4608 3 19.7697 3.38018 19.3718 4.00751C18.974 4.63465 18.9248 5.42194 19.2413 6.09405C19.558 6.76598 20.1964 7.22924 20.9334 7.32182C20.933 7.32788 20.933 7.33395 20.9334 7.34001V9.849C20.933 9.85507 20.933 9.86113 20.9334 9.86719C20.2311 9.95903 19.6177 10.3877 19.2905 11.0159C18.9633 11.6442 18.9633 12.3924 19.2905 13.0206C19.6177 13.6489 20.2311 14.0775 20.9334 14.1694V16.6784V16.6782C20.1963 16.7707 19.558 17.234 19.2413 17.9061C18.9248 18.578 18.974 19.3653 19.3718 19.9926C19.7696 20.6198 20.4608 21 21.2037 21C21.9466 21 22.6378 20.6198 23.0356 19.9926C23.4335 19.3653 23.4827 18.578 23.1662 17.9061C22.8495 17.234 22.2111 16.7707 21.4739 16.6782V14.1876C22.0012 14.1214 22.4859 13.8639 22.8361 13.4641C23.1862 13.0641 23.3772 12.5497 23.373 12.0182L23.373 12.0183Z" fill="white"/>
+</svg>
diff --git a/visualpython/img/apps/apps_descStats.svg b/visualpython/img/apps/apps_descStats.svg
new file mode 100644
index 00000000..0ff04348
--- /dev/null
+++ b/visualpython/img/apps/apps_descStats.svg
@@ -0,0 +1,13 @@
+<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M4.79171 13.9264V15.1957H3.2948V16.6304H25V15.1957H22.7546V10.4711L25 8.31892L23.8773 7.36586L22.7546 8.44195V3H18.2639V10.1351L16.0185 9.41769V5.15217H11.5278V9.59839L9.28244 10.5208V7.30435H4.79171V12.3655L3 13.1015L3.5896 14.4202L4.79171 13.9264ZM21.2577 4.43478H19.7608V10.6134L20.3071 10.7879L21.2577 9.87673V4.43478ZM20.7114 12.4295L19.7608 12.1258V15.1957H21.2577V11.9059L20.7114 12.4295ZM18.2639 11.6475L16.0185 10.9301V15.1957H18.2639V11.6475ZM14.5216 6.58696H13.0247V8.98349L13.74 8.68972L14.5216 8.93946V6.58696ZM13.8064 10.2234L13.0247 10.5444V15.1957H14.5216V10.4518L13.8064 10.2234ZM11.5278 11.1593L9.28244 12.0817V15.1957H11.5278V11.1593ZM7.78553 8.73913H6.28862V11.7506L7.78553 11.1357V8.73913ZM7.78553 12.6966L6.28862 13.3116V15.1957H7.78553V12.6966Z" fill="white"/>
+<path d="M4.79171 20.6957H15.2701V22.1304H4.79171V20.6957Z" fill="white"/>
+<path d="M4.79171 17.8261H15.2701V19.2609H4.79171V17.8261Z" fill="white"/>
+<path d="M4.79171 23.5652H15.2701V25H4.79171V23.5652Z" fill="white"/>
+<path d="M16.767 23.5652H19.0124V25H16.767V23.5652Z" fill="white"/>
+<path d="M16.767 20.6957H19.0124V22.1304H16.767V20.6957Z" fill="white"/>
+<path d="M16.767 17.8261H19.0124V19.2609H16.767V17.8261Z" fill="white"/>
+<path d="M20.5093 17.8261H22.7546V19.2609H20.5093V17.8261Z" fill="white"/>
+<path d="M20.5093 20.6957H22.7546V22.1304H20.5093V20.6957Z" fill="white"/>
+<path d="M20.5093 23.5652H22.7546V25H20.5093V23.5652Z" fill="white"/>
+<path d="M28 0H0V28H28V0Z" fill="white" fill-opacity="0.01"/>
+</svg>
diff --git a/visualpython/img/apps/apps_equalVarTest.svg b/visualpython/img/apps/apps_equalVarTest.svg
new file mode 100644
index 00000000..45667cbb
--- /dev/null
+++ b/visualpython/img/apps/apps_equalVarTest.svg
@@ -0,0 +1,7 @@
+<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M28 0H0V28H28V0Z" fill="white" fill-opacity="0.01"/>
+<path d="M2 24H26" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M2 22C3.48837 21.6034 3.86047 20.3833 4.23256 19.2238C8.60385 5.60263 11.4243 5.58148 16.1395 19.2238C16.4137 20.017 16.8837 21.6034 18 22" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M10 22C11.4884 21.6034 11.8605 20.3833 12.2326 19.2238C16.6038 5.60263 19.4243 5.58148 24.1395 19.2238C24.4137 20.017 24.8837 21.6034 26 22" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M18 6V3V4.5H10V3V6" stroke="white"/>
+</svg>
diff --git a/visualpython/img/apps/apps_factorAnalysis.svg b/visualpython/img/apps/apps_factorAnalysis.svg
new file mode 100644
index 00000000..2d1a9d42
--- /dev/null
+++ b/visualpython/img/apps/apps_factorAnalysis.svg
@@ -0,0 +1,4 @@
+<svg width="29" height="28" viewBox="0 0 29 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M28 0H0V28H28V0Z" fill="white" fill-opacity="0.01"/>
+<path d="M28 24L21.9714 18.4242C19.9811 20.5631 17.0888 21.9091 13.8696 21.9091C7.86647 21.9091 3 17.2284 3 11.4545C3 5.68066 7.86647 1 13.8696 1C19.8727 1 24.7154 5.68066 24.7154 11.4545V12.1515L16.7681 12.0325M12.3373 7C12.3373 8.34724 11.2018 9.43939 9.80108 9.43939C8.40036 9.43939 7.26485 8.34724 7.26485 7C7.26485 5.65276 8.40036 4.56061 9.80108 4.56061C11.2018 4.56061 12.3373 5.65276 12.3373 7ZM12.3373 7H19.3043V12.0705M16.7681 12.0325C16.7681 13.1873 15.7948 14.1234 14.5942 14.1234C13.3936 14.1234 12.4203 13.1873 12.4203 12.0325C12.4203 10.8778 13.3936 9.94163 14.5942 9.94163C15.7948 9.94163 16.7681 10.8778 16.7681 12.0325ZM14.5942 17.7273C14.5942 18.882 13.6209 19.8182 12.4203 19.8182C11.2197 19.8182 10.2464 18.882 10.2464 17.7273C10.2464 16.5725 11.2197 15.6364 12.4203 15.6364C13.6209 15.6364 14.5942 16.5725 14.5942 17.7273ZM14.5942 17.7273H18.942V15.6364" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>
diff --git a/visualpython/img/apps/apps_logisticRegression.svg b/visualpython/img/apps/apps_logisticRegression.svg
new file mode 100644
index 00000000..d98a6c50
--- /dev/null
+++ b/visualpython/img/apps/apps_logisticRegression.svg
@@ -0,0 +1,12 @@
+<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M28 0H0V28H28V0Z" fill="white" fill-opacity="0.01"/>
+<path d="M3.5 3.5V24.5H24.5" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M5 19.7501C4.58579 19.7501 4.25 20.0859 4.25 20.5001C4.25 20.9143 4.58579 21.2501 5 21.2501V19.7501ZM11.2913 19.2547L11.0459 19.9635L12.4634 20.4541L12.7087 19.7454L11.2913 19.2547ZM17.2087 6.7454C17.3442 6.35398 17.1368 5.92683 16.7453 5.79133C16.3539 5.65584 15.9268 5.86331 15.7913 6.25474L17.2087 6.7454ZM24.5 7.25C24.9142 7.25003 25.25 6.91426 25.25 6.50005C25.25 6.08583 24.9143 5.75003 24.5 5.75L24.5 7.25ZM5.5 21.2501C5.91421 21.2501 6.25 20.9143 6.25 20.5001C6.25 20.0859 5.91421 19.7501 5.5 19.7501V21.2501ZM8.5 19.7501C8.08579 19.7501 7.75 20.0859 7.75 20.5001C7.75 20.9143 8.08579 21.2501 8.5 21.2501V19.7501ZM9.5 21.2501C9.91421 21.2501 10.25 20.9143 10.25 20.5001C10.25 20.0859 9.91421 19.7501 9.5 19.7501V21.2501ZM20.5 7.25005C20.9142 7.25005 21.25 6.91426 21.25 6.50005C21.25 6.08583 20.9142 5.75005 20.5 5.75005L20.5 7.25005ZM19.5 5.75005C19.0858 5.75005 18.75 6.08583 18.75 6.50005C18.75 6.91426 19.0858 7.25005 19.5 7.25005V5.75005ZM23.5 5.74998C23.0858 5.74998 22.75 6.08577 22.75 6.49998C22.75 6.91419 23.0858 7.24998 23.5 7.24998L23.5 5.74998ZM12.7087 19.7454L17.2087 6.7454L15.7913 6.25474L11.2913 19.2547L12.7087 19.7454ZM5 21.2501H5.5V19.7501H5V21.2501ZM8.5 21.2501H9.5V19.7501H8.5V21.2501ZM19.5 7.25005C20.1491 7.25005 19.8426 7.25005 20.5 7.25005L20.5 5.75005C19.8426 5.75005 20.1491 5.75005 19.5 5.75005L19.5 7.25005ZM20.5 5.75005H19.5V7.25005H20.5V5.75005ZM23.5 7.24998C23.804 7.24998 24.054 7.24998 24.228 7.24999C24.315 7.24999 24.383 7.24999 24.4292 7.25C24.4523 7.25 24.47 7.25 24.4819 7.25C24.4879 7.25 24.4924 7.25 24.4954 7.25C24.4969 7.25 24.4981 7.25 24.4988 7.25C24.4992 7.25 24.4995 7.25 24.4997 7.25C24.4998 7.25 24.4998 7.25 24.4999 7.25C24.4999 7.25 24.4999 7.25 24.4999 7.25C24.4999 7.25 24.4999 7.25 24.4999 7.25C24.5 7.25 24.5 7.25 24.5 6.5C24.5 5.75 24.5 5.75 24.5 5.75C24.5 5.75 24.5 5.75 24.5 5.75C24.5 5.75 24.5 5.75 24.5 5.75C24.4999 5.75 24.4999 5.75 24.4998 5.75C24.4996 5.75 24.4993 5.75 24.4989 5.75C24.4981 5.75 24.497 5.75 24.4955 5.75C24.4925 5.75 24.488 5.75 24.482 5.75C24.4701 5.75 24.4524 5.75 24.4293 5.75C24.3831 5.74999 24.3151 5.74999 24.2281 5.74999C24.0541 5.74998 23.8041 5.74998 23.5 5.74998L23.5 7.24998Z" fill="white"/>
+<circle cx="7" cy="20.5" r="1" stroke="white"/>
+<circle cx="16" cy="20.5" r="1" stroke="white"/>
+<circle cx="22" cy="20.5" r="1" stroke="white"/>
+<circle cx="13" cy="6.5" r="1" stroke="white"/>
+<circle cx="22" cy="6.5" r="1" stroke="white"/>
+<circle cx="18" cy="6.5" r="1" stroke="white"/>
+<circle cx="11" cy="20.5" r="1" stroke="white"/>
+</svg>
diff --git a/visualpython/img/apps/apps_probDist.svg b/visualpython/img/apps/apps_probDist.svg
new file mode 100644
index 00000000..754436ad
--- /dev/null
+++ b/visualpython/img/apps/apps_probDist.svg
@@ -0,0 +1,6 @@
+<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M28 0H0V28H28V0Z" fill="white" fill-opacity="0.01"/>
+<path d="M3 23H25" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 21C5.04651 20.4509 5.55814 18.7615 6.06977 17.1561C12.0803 -1.70404 15.9584 -1.73333 22.4419 17.1561C22.8188 18.2544 23.4651 20.4509 25 21" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
+<path d="M10 12.36V23M14 23V9M18 14.04V23" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+</svg>
diff --git a/visualpython/img/apps/apps_studentstTest.svg b/visualpython/img/apps/apps_studentstTest.svg
new file mode 100644
index 00000000..402de255
--- /dev/null
+++ b/visualpython/img/apps/apps_studentstTest.svg
@@ -0,0 +1,6 @@
+<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M28 0H0V28H28V0Z" fill="white" fill-opacity="0.01"/>
+<path d="M21.1125 5.02669C21.0375 4.96808 21 4.92622 21 4.9011C21 4.86761 21.0161 4.80063 21.0482 4.70016C21.0804 4.59969 21.1072 4.54108 21.1286 4.52433C21.1501 4.48247 21.1983 4.46154 21.2733 4.46154C21.3483 4.46154 21.6217 4.45735 22.0932 4.44898H22.9453L23.2508 3.44427C23.283 3.35217 23.3205 3.23077 23.3633 3.08006C23.4062 2.92936 23.4437 2.82051 23.4759 2.75353C23.508 2.68655 23.5402 2.60283 23.5723 2.50235C23.6045 2.40188 23.6474 2.33072 23.701 2.28885C23.7546 2.24699 23.8081 2.19676 23.8617 2.13815C23.9153 2.07954 23.9904 2.04186 24.0868 2.02512C24.1833 2.00837 24.2797 2 24.3762 2C24.5691 2.00837 24.7031 2.05442 24.7781 2.13815C24.8532 2.22187 24.8907 2.30141 24.8907 2.37677C24.8907 2.43537 24.8424 2.63213 24.746 2.96703C24.6495 3.30194 24.5423 3.63265 24.4244 3.95918L24.2637 4.42386C24.2637 4.44061 24.5316 4.44898 25.0675 4.44898H25.8875C25.9625 4.50759 26 4.55364 26 4.58713C26 4.77132 25.9303 4.91784 25.791 5.02669H24.0707L23.492 6.84772C23.074 8.18734 22.865 8.90738 22.865 9.00785C22.865 9.3595 23.0096 9.53532 23.299 9.53532C23.6742 9.53532 24.0386 9.39299 24.3923 9.10832C24.746 8.82365 25.03 8.472 25.2444 8.05338C25.2658 8.00314 25.2926 7.97384 25.3248 7.96546C25.3569 7.95709 25.4373 7.94872 25.5659 7.94035H25.6302C25.791 7.94035 25.8714 7.97384 25.8714 8.04082C25.8714 8.06593 25.8553 8.11617 25.8232 8.19152C25.7696 8.32548 25.6731 8.48456 25.5338 8.66876C25.3944 8.85296 25.2122 9.04971 24.9871 9.25903C24.7621 9.46834 24.4887 9.64417 24.1672 9.7865C23.8457 9.92883 23.5188 10 23.1865 10C22.865 10 22.5595 9.93721 22.2701 9.81162C21.9807 9.68603 21.7717 9.46834 21.6431 9.15856C21.6217 9.09158 21.6109 8.97855 21.6109 8.81947V8.59341L22.1736 6.83516C22.5488 5.65463 22.7417 5.06018 22.7524 5.05181C22.7524 5.03506 22.4791 5.02669 21.9325 5.02669H21.1125Z" fill="white"/>
+<path d="M3 25H25" stroke="white" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
+<path d="M3 22.8889C5.04651 22.3737 5.55814 20.7886 6.06977 19.2823C6.39618 18.3212 6.71631 17.4123 7.0309 16.5556M25 22.8889C23.4651 22.3736 22.8188 20.3127 22.4419 19.2823C22.0903 18.3212 21.7464 17.4123 21.4093 16.5556M7.0309 16.5556V25M7.0309 16.5556C12.1951 2.49122 15.869 2.47175 21.4093 16.5556M21.4093 16.5556V25" stroke="white" stroke-width="1.5" stroke-linecap="round"/>
+</svg>
diff --git a/visualpython/img/radio_checked.svg b/visualpython/img/radio_checked.svg
new file mode 100644
index 00000000..7de7eba1
--- /dev/null
+++ b/visualpython/img/radio_checked.svg
@@ -0,0 +1,4 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="7.5" cy="7.5" r="6" stroke="#F38607"/>
+<circle cx="7.5" cy="7.5" r="4.5" fill="#F38607"/>
+</svg>
diff --git a/visualpython/img/radio_unchecked.svg b/visualpython/img/radio_unchecked.svg
new file mode 100644
index 00000000..af64b40a
--- /dev/null
+++ b/visualpython/img/radio_unchecked.svg
@@ -0,0 +1,3 @@
+<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg">
+<circle cx="7.5" cy="7.5" r="6" stroke="#828282"/>
+</svg>
diff --git a/visualpython/js/com/com_Config.js b/visualpython/js/com/com_Config.js
index 49d00fb4..49ce3b41 100644
--- a/visualpython/js/com/com_Config.js
+++ b/visualpython/js/com/com_Config.js
@@ -195,6 +195,18 @@ define([
                 'joblib': {
                     code: 'import joblib',
                     type: 'package'
+                },
+                'scipy.stats': {
+                    code: 'from scipy import stats',
+                    type: 'package'
+                },
+                'scipy': {
+                    code: 'import scipy',
+                    type: 'package'
+                },
+                'statsmodels.api': {
+                    code: 'import statsmodels.api as sm',
+                    type: 'package'
                 }
             }
 
@@ -250,7 +262,9 @@ define([
             let packageAlias = {
                 '_vp_np': 'np',
                 '_vp_pd': 'pd',
-                '_vp_plt': 'plt'
+                '_vp_plt': 'plt',
+                '_vp_stats': 'stats',
+                '_vp_sm': 'sm'
             }
 
             for (let i = 0; i < tmpList.length; i += 2) {
diff --git a/visualpython/js/com/com_generatorV2.js b/visualpython/js/com/com_generatorV2.js
index 9cf28b06..4157b2f1 100644
--- a/visualpython/js/com/com_generatorV2.js
+++ b/visualpython/js/com/com_generatorV2.js
@@ -165,10 +165,15 @@ define([
         var tblContent = $('<td></td>');
 
         
-        let { name, label, component, required } = obj;
+        let { name, label, component, required, output } = obj;
         let value = state[name];
         
-        var requiredFontStyle = required == true? 'vp-orange-text' : '';
+        var requiredFontStyle = '';
+        if (required === true) {
+            requiredFontStyle = 'vp-bold vp-orange-text';
+        } else if (output === true) {
+            requiredFontStyle = 'vp-bold';
+        }
         var lblTag = $(`<label class="vp-bold">${label}</label>`).attr({
             'for': name,
             'class': requiredFontStyle,
@@ -823,7 +828,7 @@ define([
      * @param {array/boolean} columnWithIndex boolean array or value to decide whether select tag has index option
      * Usage : 
      *  $(document).on('change', this.wrapSelector('#dataframe_tag_id'), function() {
-     *      pdGen.vp_bindColumnSource(that, 'dataframe_tag_id', ['column_input_id'], 'select', [true, true, true]);
+     *      pdGen.vp_bindColumnSource(that, 'dataframe_tag_id', ['column_input_id'], 'select', false, false);
      *  });
      */
     var vp_bindColumnSource = function(pageThis, targetId, columnInputIdList, tagType="input", columnWithEmpty=false, columnWithIndex=false) {
@@ -915,7 +920,7 @@ define([
                             'class': 'vp-select vp-state'
                         });
                         // make tag
-                        list.forEach(listVar => {
+                        list.forEach((listVar, idx) => {
                             var option = document.createElement('option');
                             $(option).attr({
                                 'value':listVar.value,
@@ -932,6 +937,7 @@ define([
                         $(pageThis.wrapSelector('#' + columnInputId)).replaceWith(function() {
                             return $(tag);
                         });
+                        $(pageThis.wrapSelector('#' + columnInputId)).trigger('change');
                     }
                 }).catch(function(err) {
                     vpLog.display(VP_LOG_TYPE.ERROR, 'com_generator - bindColumnSource error ', err)
diff --git a/visualpython/js/com/component/DataSelector.js b/visualpython/js/com/component/DataSelector.js
index 4da01526..ca520774 100644
--- a/visualpython/js/com/component/DataSelector.js
+++ b/visualpython/js/com/component/DataSelector.js
@@ -54,10 +54,12 @@ define([
                 pageThis: null, // target's page object
                 id: '',         // target id
                 value: null,    // pre-defined value
+                withPopup: true, // with filter button to show simple subset popup
                 finish: null,   // callback after selection (value, dtype)
                 select: null,   // callback after selection from suggestInput (value, dtype)
                 allowDataType: null, // list of allowed data types
                 // additional options
+                boxClasses: '',
                 classes: '',
                 attrs: '',
                 placeholder: 'Select variable',
@@ -95,7 +97,7 @@ define([
             }
 
             this._target = null;
-            if (this.prop.pageThis) {
+            if (this.prop.pageThis && this.prop.id !== '') {
                 this._target = this.prop.pageThis.wrapSelector('#' + this.prop.id);
             }
 
@@ -183,7 +185,7 @@ define([
                     autoFocus: true,
                     minLength: 0,
                     source: function (req, res) {
-                        var srcList = varList;
+                        var srcList = varList.filter(obj => that.prop.allowDataType.includes(obj.dtype));
                         var returlList = new Array();
                         for (var idx = 0; idx < srcList.length; idx++) {
                             // srcList as object array
@@ -358,20 +360,35 @@ define([
         templateForTarget() {
             let value = this.prop.value;
             if (value == undefined) {
-                value = this.prop.pageThis.state[this.prop.id] || '';
+                if (this.prop.id !== '') {
+                    value = this.prop.pageThis.state[this.prop.id] || '';
+                } else {
+                    value = '';
+                }
+            }
+            if (this.prop.withPopup === true) {
+                return `
+                    <div class="vp-ds-box vp-ds-box-${this.uuid} vp-ds-uninit ${this.prop.boxClasses}">
+                        <input type="text" class="vp-ds-target vp-input vp-state ${this.prop.classes}" 
+                                ${this.prop.attrs} 
+                                id="${this.prop.id}" value="${value}" 
+                                placeholder="${this.prop.placeholder}" ${this.prop.required?'required="required"':''}/>
+                        <span class="vp-ds-filter">
+                        <!-- LAB: img to url -->
+                        <!-- <img src="${com_Const.IMAGE_PATH}filter.svg"/> -->
+                        </span>
+                    </div>
+                `;
+            } else {
+                return `
+                    <div class="vp-ds-box vp-ds-box-${this.uuid} vp-ds-uninit ${this.prop.boxClasses}">
+                        <input type="text" class="vp-ds-target vp-input vp-state ${this.prop.classes}" 
+                                ${this.prop.attrs} 
+                                id="${this.prop.id}" value="${value}" 
+                                placeholder="${this.prop.placeholder}" ${this.prop.required?'required="required"':''}/>
+                    </div>
+                `;
             }
-            return `
-                <div class="vp-ds-box vp-ds-box-${this.uuid} vp-ds-uninit">
-                    <input type="text" class="vp-ds-target vp-input vp-state ${this.prop.classes}" 
-                            ${this.prop.attrs} 
-                            id="${this.prop.id}" value="${value}" 
-                            placeholder="${this.prop.placeholder}" ${this.prop.required?'required="required"':''}/>
-                    <span class="vp-ds-filter">
-                    <!-- LAB: img to url -->
-                    <!-- <img src="${com_Const.IMAGE_PATH}filter.svg"/> -->
-                    </span>
-                </div>
-            `;
         }
 
         templateForMultiSelector() {
diff --git a/visualpython/js/com/component/MultiSelector.js b/visualpython/js/com/component/MultiSelector.js
index f4f4c2ce..947122a3 100644
--- a/visualpython/js/com/component/MultiSelector.js
+++ b/visualpython/js/com/component/MultiSelector.js
@@ -45,6 +45,13 @@ define([
     //========================================================================
     // [CLASS] MultiSelector
     //========================================================================
+    /**
+     * MultiSelector
+     * Usage
+     * this._columnSelector = new MultiSelector(this.wrapSelector('#multi-selector-id'),
+                        { mode: 'columns', parent: [data], selectedList: this.state.indexing, allowAdd: true }
+                    );
+     */
     class MultiSelector extends Component {
 
         /**
@@ -62,19 +69,31 @@ define([
             // configuration
             this.config = this.state;
 
-            var { mode, type, parent, dataList=[], selectedList=[], includeList=[], excludeList=[], allowAdd=false } = this.config;
+            var { 
+                mode, type, parent, 
+                dataList=[], selectedList=[], includeList=[], excludeList=[], 
+                allowAdd=false, showDescription=true,
+                change=null
+            } = this.config;
             this.mode = mode;   // variable / columns / index / ndarray0 / ndarray1 / methods / data(given data)
             this.parent = parent;
             this.selectedList = selectedList;
             this.includeList = includeList;
             this.excludeList = excludeList;
-            this.allowAdd = allowAdd;
+            this.allowAdd = allowAdd;                    // allow adding new item
+            this.showDescription = showDescription; // show description on the top of the box
+
+            this.change = change;  // function (type=('add'|'remove'), list=[])
 
             this.dataList = dataList;   // [ { value, code, type }, ... ]
             this.pointer = { start: -1, end: -1 };
 
             var that = this;
 
+            if (parent === '') {
+                this._executeCallback([]);
+                return;
+            }
             switch (mode) {
                 case 'columns':
                     this._getColumnList(parent, function(dataList) {
@@ -238,6 +257,7 @@ define([
             $(this.frameSelector).html(this.render());
             this.bindEvent();
             this.bindDraggable();
+            this._bindItemClickEvent();
         }
 
         getDataList() {
@@ -260,7 +280,9 @@ define([
             var that = this;
 
             var tag = new com_String();
-            tag.appendLine('<label>Drag-and-drop columns to right to select.</label>');
+            if (this.showDescription === true) {
+                tag.appendLine('<label>Drag-and-drop columns to right to select.</label>');
+            }
             tag.appendFormatLine('<div class="{0} {1}">', APP_SELECT_CONTAINER, this.uuid);
             // select - left
             tag.appendFormatLine('<div class="{0}">', APP_SELECT_LEFT);
@@ -275,6 +297,7 @@ define([
                 $(this.wrapSelector()).val(value);
                 $(this.wrapSelector()).trigger('change');
             });
+            vpSearchSuggest.setAutoFocus(false);
             vpSearchSuggest.setNormalFilter(true);
             tag.appendLine(vpSearchSuggest.toTagString());
             tag.appendFormatLine('<i class="fa fa-search search-icon"></i>')
@@ -378,6 +401,7 @@ define([
 
                 // draggable
                 that.bindDraggable();
+                that._bindItemClickEvent();
             });
 
             // item indexing - add all
@@ -388,6 +412,8 @@ define([
                 $(that.wrapSelector('.' + APP_SELECT_ITEM)).addClass('added');
                 $(that.wrapSelector('.' + APP_SELECT_ITEM + '.selected')).removeClass('selected');
                 that.pointer = { start: -1, end: -1 };
+
+                that.change && that.change('add', that.getDataList());
             });
 
             // item indexing - add
@@ -400,6 +426,8 @@ define([
                 $(that.wrapSelector('.' + APP_SELECT_ITEM + selector)).addClass('added');
                 $(that.wrapSelector('.' + APP_SELECT_ITEM + selector)).removeClass('selected');
                 that.pointer = { start: -1, end: -1 };
+
+                that.change && that.change('add', that.getDataList());
             });
 
             // item indexing - del
@@ -420,6 +448,8 @@ define([
                 selectedTag.removeClass('added');
                 selectedTag.removeClass('selected');
                 that.pointer = { start: -1, end: -1 };
+
+                that.change && that.change('remove', that.getDataList());
             });
 
             // item indexing - delete all
@@ -437,12 +467,16 @@ define([
                 $(that.wrapSelector('.' + APP_SELECT_ITEM)).removeClass('added');
                 $(that.wrapSelector('.' + APP_SELECT_ITEM + '.selected')).removeClass('selected');
                 that.pointer = { start: -1, end: -1 };
+
+                that.change && that.change('remove', that.getDataList());
             });
 
             // add new item
             $(this.wrapSelector('.vp-cs-add-item-btn')).on('click', function(event) {
                 let newItemName = $(that.wrapSelector('.vp-cs-add-item-name')).val();
                 that._addNewItem(newItemName);
+
+                that.change && that.change('add', that.getDataList());
             });
             // add new item (by pushing enter key)
             $(this.wrapSelector('.vp-cs-add-item-name')).on('keyup', function(event) {
@@ -452,6 +486,8 @@ define([
                 if (keycode == 13) { // enter
                     let newItemName = $(this).val();
                     that._addNewItem(newItemName);
+
+                    that.change && that.change('add', that.getDataList());
                 }
             });
 
@@ -522,6 +558,8 @@ define([
             $(this.wrapSelector('.vp-cs-del-item')).on('click', function(event) {
                 $(this).closest('.' + APP_SELECT_ITEM).remove();
                 that.pointer = { start: -1, end: -1 };
+
+                that.change && that.change('remove', that.getDataList());
             });
         }
 
@@ -629,6 +667,7 @@ define([
                     if ($(this).hasClass('right')) {
                         // add
                         $(dropGroup).addClass('added');
+                        that.change && that.change('add', that.getDataList());
                     } else {
                         // del
                         $(dropGroup).removeClass('added');
@@ -636,6 +675,7 @@ define([
                         $(droppedOn).find('.' + APP_SELECT_ITEM).sort(function(a, b) {
                             return ($(b).data('idx')) < ($(a).data('idx')) ? 1 : -1;
                         }).appendTo( $(droppedOn) );
+                        that.change && that.change('remove', that.getDataList());
                     }
                     // remove selection
                     $(droppableQuery).find('.selected').removeClass('selected');
diff --git a/visualpython/js/com/component/PopupComponent.js b/visualpython/js/com/component/PopupComponent.js
index 67971c26..57873c37 100644
--- a/visualpython/js/com/component/PopupComponent.js
+++ b/visualpython/js/com/component/PopupComponent.js
@@ -288,6 +288,13 @@ define([
             }
         }
 
+        addCheckModules(module) {
+            if (this.config.checkModules.includes(module)) {
+                return ;
+            }
+            this.config.checkModules.push(module);
+        }
+
         _bindEvent() {
             var that = this;
             // Close popup event
@@ -307,6 +314,7 @@ define([
             });
             // Toggle operation (minimize)
             $(this.wrapSelector('.vp-popup-toggle')).on('click', function(evt) {
+                evt.stopPropagation();
                 $(that.eventTarget).trigger({
                     type: 'close_option_page',
                     component: that
@@ -704,7 +712,37 @@ define([
         }
 
         loadState() {
-            /** Implementation needed */
+            vpLog.display(VP_LOG_TYPE.DEVELOP, this.state);   
+
+            let that = this;
+            Object.keys(this.state).forEach(key => {
+                if (key && key !== '' && key !== 'config') {
+                    let tag = $(that.wrapSelector('#' + key) + ', ' + that.wrapSelector('input[name="' + key + '"]'));
+                    let tagName = $(tag).prop('tagName');
+                    let savedValue = that.state[key];
+                    switch(tagName) {
+                        case 'INPUT':
+                            let inputType = $(tag).prop('type');
+                            if (inputType === 'text' || inputType === 'number' || inputType === 'hidden') {
+                                $(tag).val(savedValue);
+                                break;
+                            }
+                            if (inputType === 'checkbox') {
+                                $(tag).prop('checked', savedValue);
+                                break;
+                            }
+                            if (inputType === 'radio') {
+                                $(tag).filter(`[value="${savedValue}"]`).prop('checked', true);
+                            }
+                            break;
+                        case 'TEXTAREA':
+                        case 'SELECT':
+                        default:
+                            $(tag).val(savedValue);
+                            break;
+                    }
+                }
+            });
         }
 
         saveState() {
@@ -726,6 +764,15 @@ define([
                     let inputType = $(tag).prop('type');
                     if (inputType == 'checkbox') {
                         newValue = $(tag).prop('checked');
+                    } else if (inputType == 'radio') {
+                        let radioGroup = $(tag).prop('name');
+                        let checked = $(tag).prop('checked');
+                        if (checked === true) {
+                            id = radioGroup;
+                            newValue = $(tag).val();
+                        } else {
+                            return ;
+                        }
                     } else {
                         // inputType == 'text' || inputType == 'number' || inputType == 'hidden' || inputType == 'color' || inputType == 'range'
                         newValue = $(tag).val();
@@ -780,7 +827,7 @@ define([
          */
         checkRequiredOption() {
             let requiredFilled = true;
-            let requiredTags = $(this.wrapSelector('input[required=true]') + ',' + this.wrapSelector('input[required=required]'));
+            let requiredTags = $(this.wrapSelector('input[required=true]:visible') + ',' + this.wrapSelector('input[required=required]:visible'));
 
             vpLog.display(VP_LOG_TYPE.DEVELOP, 'checkRequiredOption', this, requiredTags);
 
@@ -812,9 +859,9 @@ define([
                         let checkedList = JSON.parse(result);
                         let executeList = [];
                         checkedList && checkedList.forEach((mod, idx) => {
-                            if (mod == false) {
+                            if (mod === false) {
                                 let modInfo = vpConfig.getModuleCode(checkModules[idx]);
-                                if (modInfo) {
+                                if (modInfo && modInfo?.code !== '') {
                                     executeList.push(modInfo.code);
                                 }
                             }
@@ -908,7 +955,7 @@ define([
         save() {
             if (this.prop.finish && typeof this.prop.finish == 'function') {
                 var code = this.generateCode();
-                this.prop.finish(code);
+                this.prop.finish(code, this.state);
             }
             $(this.eventTarget).trigger({
                 type: 'apply_option_page', 
@@ -943,11 +990,27 @@ define([
             $(this.wrapSelector()).show();
         }
 
+        showInstallButton() {
+            $(this.wrapSelector('#popupInstall')).show();
+        }
+
+        showImportButton() {
+            $(this.wrapSelector('#popupImport')).show();
+        }
+
         hide() {
             this.taskItem && this.taskItem.blurItem();
             $(this.wrapSelector()).hide();
         }
 
+        hideInstallButton() {
+            $(this.wrapSelector('#popupInstall')).hide();
+        }
+
+        hideImportButton() {
+            $(this.wrapSelector('#popupImport')).hide();
+        }
+
         isHidden() {
             return !$(this.wrapSelector()).is(':visible');
         }
diff --git a/visualpython/js/com/component/SuggestInput.js b/visualpython/js/com/component/SuggestInput.js
index ef9aec35..a0ae45f8 100644
--- a/visualpython/js/com/component/SuggestInput.js
+++ b/visualpython/js/com/component/SuggestInput.js
@@ -17,6 +17,7 @@ define([
             this._placeholder = "Select variable";
             this._compID = "";
             this._additionalClass = "";
+            this._autoFocus = true;
             this._normalFilter = true;
             this._suggestList = new Array();
             this._selectEvent = undefined;
@@ -48,6 +49,13 @@ define([
         setComponentID(compID = "") {
             this._compID = compID;
         }
+        /**
+         * set auto focus on enter
+         * @param {boolean} autoFocus 
+         */
+        setAutoFocus(autoFocus = true) {
+            this._autoFocus = autoFocus;
+        }
         /**
          * normal filter usage
          * @param {String} normalFilter 
@@ -118,7 +126,7 @@ define([
                 $(com_util.formatString(".{0}", that.uuid)).removeClass('suggest-input-uninit').addClass('suggest-input');
 
                 $(com_util.formatString(".{0}", that.uuid)).autocomplete({
-                    autoFocus: true,
+                    autoFocus: that._autoFocus,
                     minLength: minLength,
                     source: function (req, res) {
                         var srcList = typeof that._suggestList == "function" ? that._suggestList() : that._suggestList;
diff --git a/visualpython/js/m_apps/File.js b/visualpython/js/m_apps/File.js
index 95cd0e39..e6bf25dd 100644
--- a/visualpython/js/m_apps/File.js
+++ b/visualpython/js/m_apps/File.js
@@ -42,7 +42,9 @@ define([
                 'csv': 'csv',
                 'excel': 'xlsx',
                 'json': 'json',
-                'pickle': ''
+                'pickle': '',
+                'sas': '', // xport or sas7bdat
+                'spss': ''
             }
             
             this.package = {
@@ -79,10 +81,12 @@ define([
             this.fileState = {
                 'Read': {
                     fileTypeId: {
-                        'csv': 'pd004',
-                        'excel': 'pd123',
-                        'json': 'pd076',
-                        'pickle': 'pd079'
+                        'csv': 'pd_readCsv',
+                        'excel': 'pd_readExcel',
+                        'json': 'pd_readJson',
+                        'pickle': 'pd_readPickle',
+                        'sas': 'pd_readSas',
+                        'spss': 'pd_readSpss'
                     },
                     selectedType: 'csv',
                     package: null,
@@ -93,10 +97,10 @@ define([
                 },
                 'Write': {
                     fileTypeId: {
-                        'csv': 'pd005',
-                        'excel': 'pd124',
-                        'json': 'pd077',
-                        'pickle': 'pd078'
+                        'csv': 'pd_toCsv',
+                        'excel': 'pd_toExcel',
+                        'json': 'pd_toJson',
+                        'pickle': 'pd_toPickle'
                     },
                     selectedType: 'csv',
                     package: null,
@@ -205,6 +209,18 @@ define([
                 // reload
                 that.renderPage(pageType);
                 that._bindEventByType(pageType);
+
+                if (value === 'spss') {
+                    // show install button
+                    that.showInstallButton();
+                    // show install note below File type selection
+                    $(`<tr><td colspan="2">
+                        <label class="vp-orange-text vp-italic">NOTE: </label>
+                        <label class="vp-gray-text vp-italic">pyreadstat package is required to read spss file.</label>
+                    </td></tr>`).insertAfter($(that.wrapSelector('#fileType')).closest('tr'));
+                } else {
+                    that.hideInstallButton();
+                }
             });
     
             // open file navigation
@@ -316,6 +332,13 @@ define([
             this.fileResultState = {
                 ...this.fileState[pageType].fileResultState
             };
+
+            if (selectedType == 'pickle') {
+                // hide additional option box
+                $(this.wrapSelector(prefix + '#vp_optionBox')).closest('.vp-accordian-container').hide();
+            } else {
+                $(this.wrapSelector(prefix + '#vp_optionBox')).closest('.vp-accordian-container').show();
+            }
     
             if (pageType == 'Write') {
                 if (selectedType == 'json') {
@@ -333,7 +356,7 @@ define([
     
             // prepend file type selector
             $(this.wrapSelector(prefix + '#vp_inputOutputBox table tbody')).prepend(
-                $('<tr>').append($(`<td><label for="fileType" class="vp-orange-text">File Type</label></td>`))
+                $('<tr>').append($(`<td><label for="fileType" class="vp-bold vp-orange-text">File Type</label></td>`))
                     .append($('<td><select id="fileType" class="vp-select"></select></td>'))
             );
             var fileTypeList = Object.keys(fileTypeObj);
@@ -397,8 +420,6 @@ define([
                 suggestInput.setPlaceholder('encoding option');
                 return suggestInput.toTagString();
             });
-    
-            
         }
 
         render() {
@@ -420,6 +441,10 @@ define([
 
         }
 
+        generateInstallCode() {
+            return [ '!pip install pyreadstat' ];
+        }
+
         generateCode() {
             var pageType = $(this.wrapSelector('#vp_fileioType')).val();
             var sbCode = new com_String;
diff --git a/visualpython/js/m_apps/Frame.js b/visualpython/js/m_apps/Frame.js
index bdb71e4f..76659cd3 100644
--- a/visualpython/js/m_apps/Frame.js
+++ b/visualpython/js/m_apps/Frame.js
@@ -129,7 +129,8 @@ define([
                     child: [
                         { id: 'fillna', label: 'Fill NA', axis: FRAME_AXIS.COLUMN, selection: FRAME_SELECT_TYPE.NONE, menuType: FRAME_EDIT_TYPE.FILL_NA },
                         { id: 'dropna', label: 'Drop NA', axis: FRAME_AXIS.COLUMN, selection: FRAME_SELECT_TYPE.NONE, menuType: FRAME_EDIT_TYPE.DROP_NA },
-                        { id: 'drop_outlier', label: 'Drop outlier', axis: FRAME_AXIS.COLUMN, selection: FRAME_SELECT_TYPE.SINGLE, menuType: FRAME_EDIT_TYPE.DROP_OUT },
+                        { id: 'fill_outlier', label: 'Fill outlier', axis: FRAME_AXIS.COLUMN, selection: FRAME_SELECT_TYPE.MULTI, menuType: FRAME_EDIT_TYPE.FILL_OUT },
+                        { id: 'drop_outlier', label: 'Drop outlier', axis: FRAME_AXIS.COLUMN, selection: FRAME_SELECT_TYPE.MULTI, menuType: FRAME_EDIT_TYPE.DROP_OUT },
                         { id: 'drop_duplicates', label: 'Drop duplicates', axis: FRAME_AXIS.COLUMN, selection: FRAME_SELECT_TYPE.NONE, menuType: FRAME_EDIT_TYPE.DROP_DUP },
                     ]
                 },
@@ -626,8 +627,10 @@ define([
                     case FRAME_EDIT_TYPE.FILL_NA:
                     case FRAME_EDIT_TYPE.DROP_NA:
                     case FRAME_EDIT_TYPE.DROP_DUP:
+                    case FRAME_EDIT_TYPE.FILL_OUT:
                     case FRAME_EDIT_TYPE.DROP_OUT:
-                    case FRAME_EDIT_TYPE.DROP: // check one more time
+                    case FRAME_EDIT_TYPE.DROP: 
+                        // open inner popup
                         that.openInputPopup(editType);
                         break;
                     default:
@@ -714,18 +717,25 @@ define([
                     $(this.wrapSelector('.vp-inner-popup-input3')).focus();
                     return;
                 }
-            } else if (type === FRAME_EDIT_TYPE.REPLACE) {
-                if (content.input === '') {
-                    $(this.wrapSelector('.vp-inner-popup-input')).focus();
-                    return;
-                }
             } else if (type === FRAME_EDIT_TYPE.FILL_NA) {
                 if (content.method === 'value' && content.value === '') {
                     $(this.wrapSelector('.vp-inner-popup-value')).focus();
                     return;
                 }
+            } else if (type === FRAME_EDIT_TYPE.FILL_OUT) {
+                if (content.filltype === 'value' && content.fillvalue === '') {
+                    $(this.wrapSelector('.vp-inner-popup-fillvalue')).focus();
+                    return;
+                }
             }
-            if (type == FRAME_EDIT_TYPE.DROP_OUT) {
+            // run check modules for outliers and load codes
+            if (type === FRAME_EDIT_TYPE.FILL_OUT) {
+                this.config.checkModules = ['pd', 'np', 'vp_fill_outlier'];
+                let that = this;
+                this.checkAndRunModules(true).then(function() {
+                    that.loadCode(that.getTypeCode(that.state.popup.type, content));
+                });
+            } else if (type === FRAME_EDIT_TYPE.DROP_OUT) {
                 this.config.checkModules = ['pd', 'np', 'vp_drop_outlier'];
                 let that = this;
                 this.checkAndRunModules(true).then(function() {
@@ -768,39 +778,141 @@ define([
             var that = this;
 
             if (menuType === FRAME_EDIT_TYPE.ADD_COL
-                || menuType === FRAME_EDIT_TYPE.ADD_ROW) {
-                ///// add page
-                // 1. add type
-                $(this.wrapSelector('.vp-inner-popup-addtype')).on('change', function() {
-                    var tab = $(this).val();
-                    $(that.wrapSelector('.vp-inner-popup-tab')).hide();
-                    $(that.wrapSelector('.vp-inner-popup-tab.' + tab)).show();
+                || menuType === FRAME_EDIT_TYPE.ADD_ROW
+                || menuType === FRAME_EDIT_TYPE.REPLACE) {
+                // Add page
+                if (menuType === FRAME_EDIT_TYPE.ADD_COL
+                    || menuType === FRAME_EDIT_TYPE.ADD_ROW) {
+                    ///// add page
+                    // 1. add type
+                    $(this.wrapSelector('.vp-inner-popup-addtype')).on('change', function() {
+                        var tab = $(this).val();
+                        $(that.wrapSelector('.vp-inner-popup-tab')).hide();
+                        $(that.wrapSelector('.vp-inner-popup-tab.' + tab)).show();
+                    });
+            
+                    // 2-1. hide column selection box
+                    $(this.wrapSelector('.vp-inner-popup-var1box .vp-vs-data-type')).on('change', function() {
+                        var type = $(this).val();
+                        if (type == 'DataFrame') {
+                            $(that.wrapSelector('.vp-inner-popup-var1col')).show();
+                        } else {
+                            $(that.wrapSelector('.vp-inner-popup-var1col')).hide();
+                        }
+                    });
+            
+                    $(this.wrapSelector('.vp-inner-popup-var2box .vp-vs-data-type')).on('change', function() {
+                        var type = $(this).val();
+                        if (type == 'DataFrame') {
+                            $(that.wrapSelector('.vp-inner-popup-var2col')).show();
+                        } else {
+                            $(that.wrapSelector('.vp-inner-popup-var2col')).hide();
+                        }
+                    });
+
+                    $(document).off('change', this.wrapSelector('.vp-inner-popup-vartype'));
+                    $(document).on('change', this.wrapSelector('.vp-inner-popup-vartype'), function() {
+                        var type = $(this).val();
+                        $(this).closest('tr').find('.vp-inner-popup-vartype-box').hide();
+                        $(this).closest('tr').find('.vp-inner-popup-vartype-box.' + type).show();
+                    });
+                }
+
+                // Replace page
+                if (menuType === FRAME_EDIT_TYPE.REPLACE) {
+                    $(this.wrapSelector('.vp-inner-popup-replacetype')).on('change', function() {
+                        var tab = $(this).val();
+                        $(that.wrapSelector('.vp-inner-popup-tab')).hide();
+                        $(that.wrapSelector('.vp-inner-popup-tab.' + tab)).show();
+                    });
+                }
+
+                // Add & Replace page
+                // condition add
+                $(document).off('click', this.wrapSelector('.vp-inner-popup-add-cond'));
+                $(document).on('click', this.wrapSelector('.vp-inner-popup-add-cond'), function (event) {
+                    that.handleConditionAdd();
                 });
-        
-                // 2-1. hide column selection box
-                $(this.wrapSelector('.vp-inner-popup-var1box .vp-vs-data-type')).on('change', function() {
-                    var type = $(this).val();
-                    if (type == 'DataFrame') {
-                        $(that.wrapSelector('.vp-inner-popup-var1col')).show();
+
+                // condition delete
+                $(document).off('click', this.wrapSelector('.vp-inner-popup-del-cond'));
+                $(document).on('click', this.wrapSelector('.vp-inner-popup-del-cond'), function (event) {
+                    event.stopPropagation();
+    
+                    // clear previous one
+                    $(this).closest('tr').remove();
+                    $(that.wrapSelector('.vp-inner-popup-oper-connect:last')).hide();
+                });
+
+                // change column selection for condition page
+                $(document).off('change', this.wrapSelector('.vp-inner-popup-col-list'));
+                $(document).on('change', this.wrapSelector('.vp-inner-popup-col-list'), function () {
+                    var thisTag = $(this);
+                    var varName = that.state.tempObj;
+                    var colName = $(this).find('option:selected').attr('data-code');
+                    var colDtype = $(this).find('option:selected').attr('data-dtype');
+
+                    var operTag = $(this).closest('td').find('.vp-inner-popup-oper-list');
+                    var condTag = $(this).closest('td').find('.vp-inner-popup-condition');
+
+                    if (colName == '.index') {
+                        // index
+                        $(thisTag).closest('td').find('.vp-inner-popup-cond-use-text').prop('checked', false);
+                        $(operTag).replaceWith(function () {
+                            return that.templateForConditionOperator('');
+                        });
+                        $(condTag).replaceWith(function () {
+                            return that.templateForConditionCondInput([], '');
+                        });
+                        that.generateCode();
                     } else {
-                        $(that.wrapSelector('.vp-inner-popup-var1col')).hide();
+                        // get result and load column list
+                        vpKernel.getColumnCategory(varName, colName).then(function (resultObj) {
+                            let { result } = resultObj;
+                            try {
+                                var category = JSON.parse(result);
+                                if (category && category.length > 0 && colDtype == 'object') {
+                                    // if it's categorical column and its dtype is object, check 'Text' as default
+                                    $(thisTag).closest('td').find('.vp-inner-popup-cond-use-text').prop('checked', true);
+                                } else {
+                                    $(thisTag).closest('td').find('.vp-inner-popup-cond-use-text').prop('checked', false);
+                                }
+                                $(operTag).replaceWith(function () {
+                                    return that.templateForConditionOperator(colDtype);
+                                });
+                                $(condTag).replaceWith(function () {
+                                    return that.templateForConditionCondInput(category, colDtype);
+                                });
+                            } catch {
+                                $(thisTag).closest('td').find('.vp-inner-popup-cond-use-text').prop('checked', false);
+                                $(operTag).replaceWith(function () {
+                                    return that.templateForConditionOperator(colDtype);
+                                });
+                                $(condTag).replaceWith(function () {
+                                    return that.templateForConditionCondInput([], colDtype);
+                                });
+                            }
+                        });
                     }
                 });
-        
-                $(this.wrapSelector('.vp-inner-popup-var2box .vp-vs-data-type')).on('change', function() {
-                    var type = $(this).val();
-                    if (type == 'DataFrame') {
-                        $(that.wrapSelector('.vp-inner-popup-var2col')).show();
+
+                // change operator selection
+                $(document).off('change', this.wrapSelector('.vp-inner-popup-oper-list'));
+                $(document).on('change', this.wrapSelector('.vp-inner-popup-oper-list'), function () {
+                    var oper = $(this).val();
+                    var condTag = $(this).closest('td').find('.vp-inner-popup-condition');
+                    var useTextTag = $(this).closest('td').find('.vp-inner-popup-cond-use-text');
+                    // var colDtype = $(this).closest('td').find('.vp-col-list option:selected').attr('data-dtype');
+
+                    // if operator is isnull(), notnull(), disable condition input
+                    if (oper == 'isnull()' || oper == 'notnull()') {
+                        $(condTag).prop('disabled', true);
+                        $(useTextTag).prop('disabled', true);
                     } else {
-                        $(that.wrapSelector('.vp-inner-popup-var2col')).hide();
+                        $(condTag).prop('disabled', false);
+                        $(useTextTag).prop('disabled', false);
                     }
                 });
-            } else if (menuType === FRAME_EDIT_TYPE.REPLACE) {
-                $(this.wrapSelector('.vp-inner-popup-replacetype')).on('change', function() {
-                    var tab = $(this).val();
-                    $(that.wrapSelector('.vp-inner-popup-tab')).hide();
-                    $(that.wrapSelector('.vp-inner-popup-tab.' + tab)).show();
-                });
             } else if (menuType === FRAME_EDIT_TYPE.DISCRETIZE) {
                 // change bins
                 $(this.wrapSelector('.vp-inner-popup-bins')).on('change', function() {
@@ -862,6 +974,42 @@ define([
                     let tag = $(this).closest('.vp-inner-popup-sortby-item');
                     tag.insertAfter(tag.next());
                 });
+            } else if (menuType === FRAME_EDIT_TYPE.FILL_OUT) {
+                $(this.wrapSelector('.vp-inner-popup-filltype')).on('change', function() {
+                    let filltype = $(this).val();
+                    if (filltype === 'value') {
+                        $(that.wrapSelector('.vp-inner-popup-fillvalue')).prop('disabled', false);
+                    } else {
+                        $(that.wrapSelector('.vp-inner-popup-fillvalue')).prop('disabled', true);
+                    }
+                });
+            } else if (menuType === FRAME_EDIT_TYPE.DROP_NA) {
+                $(this.wrapSelector('.vp-inner-popup-how')).on('change', function() {
+                    let val = $(this).val();
+                    if (val === '') {
+                        $(that.wrapSelector('.vp-inner-popup-thresh')).prop('disabled', false);
+                    } else {
+                        $(that.wrapSelector('.vp-inner-popup-thresh')).prop('disabled', true);
+                    }
+                });
+            } else if (menuType === FRAME_EDIT_TYPE.FILL_NA) {
+                // bind event on method
+                $(this.wrapSelector('.vp-inner-popup-method')).on('change', function() {
+                    let changedVal = $(this).val();
+                    if (changedVal === 'value') {
+                        // show value row
+                        $(that.wrapSelector('.vp-inner-popup-value-row')).show();
+                        $(that.wrapSelector('.vp-inner-popup-fill-row')).hide();
+                    } else if (changedVal === 'ffill' || changedVal === 'bfill') {
+                        // show method fill row
+                        $(that.wrapSelector('.vp-inner-popup-value-row')).hide();
+                        $(that.wrapSelector('.vp-inner-popup-fill-row')).show();
+                    } else {
+                        // hide all
+                        $(that.wrapSelector('.vp-inner-popup-value-row')).hide();
+                        $(that.wrapSelector('.vp-inner-popup-fill-row')).hide();
+                    }
+                });
             }
             
         }
@@ -1186,6 +1334,8 @@ define([
                 content.appendLine('<tr><th><label>Add type</label></th>');
                 content.appendFormatLine('<td><select class="{0}">', 'vp-inner-popup-addtype');
                 content.appendFormatLine('<option value="{0}">{1}</option>', 'variable', 'Variable');
+                content.appendFormatLine('<option value="{0}">{1}</option>', 'value', 'Value');
+                content.appendFormatLine('<option value="{0}">{1}</option>', 'condition', 'Condition');
                 content.appendFormatLine('<option value="{0}">{1}</option>', 'apply', 'Apply');
                 content.appendLine('</select></td></tr>');
             }
@@ -1196,18 +1346,73 @@ define([
             
             // tab 1. variable
             content.appendFormatLine('<div class="{0} {1}">', 'vp-inner-popup-tab', 'variable');
-            content.appendLine('<table class="vp-tbl-gap5"><colgroup><col width="110px"><col width="*"></colgroup>');
+            content.appendLine('<table class="vp-tbl-gap5"><colgroup><col width="110px"><col width="*"><col width="15px"></colgroup>');
             content.appendLine('<tr class="vp-inner-popup-value-row">');
-            content.appendLine('<th><label>Variable</label></th>');
-            content.appendFormatLine('<td><input type="text" class="{0}" data-idx="{1}" placeholder="Type value"/>', 'vp-inner-popup-value', 0);
+            content.appendFormatLine('<th><select class="{0}"><option value="variable">Variable</option><option value="column">Column</option></select></th>', 'vp-inner-popup-vartype');
+            content.appendLine('<td>');
+            content.appendFormatLine('<div class="{0}">', 'vp-inner-popup-vartype-box variable');
+            content.appendFormatLine('<input type="text" class="{0}" data-idx="{1}" placeholder="Type value"/>', 'vp-inner-popup-value', 0);
             content.appendFormatLine('<label><input type="checkbox" class="{0}"/><span>{1}</span></label>', 'vp-inner-popup-istext','Text');
+            content.appendLine('</div>');
+            content.appendFormatLine('<div class="{0}" style="display:none;">', 'vp-inner-popup-vartype-box column');
+            content.appendFormatLine('<select class="vp-select {0}">', 'vp-inner-popup-var-col-list');
+            this.state.columnList.forEach(col => {
+                content.appendFormatLine('<option data-code="{0}" data-dtype="{1}" value="{2}">{3}</option>',
+                    col.code, col.type, col.code, col.label);
+            });
+            content.appendLine('</select>');
+            content.appendLine('</div>');
             // content.appendFormatLine('<span class="{0} vp-icon-close-small"></span>', 'vp-inner-popup-delete-value');
-            content.appendLine('</td></tr>');
+            content.appendLine('</td><td></td></tr>');
             content.appendFormatLine('<tr class="vp-inner-popup-addvalue-row"><td colspan="2"><button class="vp-button {0}">+ Variable</button></td></tr>', 'vp-inner-popup-addvalue');
             content.appendLine('</table>');
             content.appendLine('</div>'); // end of vp-inner-popup-tab value
 
-            // tab 2. apply
+            // tab 2. value
+            content.appendFormatLine('<div class="{0} {1}" style="display:none;">', 'vp-inner-popup-tab', 'value');
+            content.appendFormatLine('<div class="{0}">', 'vp-grid-col-120');
+            content.appendLine('<label class="vp-orange-text">Target column</label>');
+            content.appendFormatLine('<select class="vp-select {0}">', 'vp-inner-popup-value-col-list');
+            this.state.columnList.forEach(col => {
+                content.appendFormatLine('<option data-code="{0}" data-dtype="{1}" value="{2}">{3}</option>',
+                    col.code, col.type, col.code, col.label);
+            });
+            content.appendLine('</select>');
+            content.appendLine('</div>');
+            content.appendFormatLine('<label><input type="checkbox" class="{0}"/><span>{1}</span></label>', 'vp-inner-popup-use-regex', 'Use Regular Expression');
+            content.appendLine('<br/><br/>');
+            content.appendFormatLine('<div class="{0}">', 'vp-inner-popup-replace-table');
+            content.appendLine('<table class="vp-tbl-gap5">');
+            content.appendLine('<colgroup><col width="170px"><col width="170px"><col width="*"></colgroup>');
+            content.appendFormatLine('<thead><th>{0}</th><th>{1}</th><th></th></thead>', 'Value', 'New value');
+            content.appendLine('<tbody>');
+            content.appendLine(this.renderReplaceInput(0));
+            content.appendFormatLine('<tr><td colspan="3"><button class="{0} {1}">{2}</button></td></tr>', 'vp-button', 'vp-inner-popup-replace-add', '+ Add value');
+            content.appendLine('</tbody>');
+            content.appendLine('</table>');
+            content.appendLine('</div>');
+            content.appendLine('</div>');
+
+            // tab 3. condition
+            // replace page - 2. condition
+            content.appendFormatLine('<div class="{0} {1}" style="display:none;">', 'vp-inner-popup-tab', 'condition');
+            // condition table
+            content.appendLine('<table class="vp-inner-popup-condition-tbl vp-tbl-gap5 wp100"><colgroup><col width="110px"><col width="*"></colgroup>');
+            content.appendLine(this.renderReplaceCondition());
+            content.appendFormatLine('<tr><td colspan="4"><button type="button" class="vp-button {0}">{1}</button></td></tr>',
+                'vp-inner-popup-add-cond', '+ Condition');
+            content.appendLine('</table>');
+            content.appendLine('<hr style="margin: 5px 0;"/>');
+            // value
+            content.appendLine('<div class="vp-grid-col-95">');
+            content.appendLine('<label>Value</label>');
+            content.appendLine('<div>')
+            content.appendFormatLine('<input type="text" class="{0}"/>', 'vp-inner-popup-condvalue');
+            content.appendFormatLine('<label><input type="checkbox" class="{0}"/><span>{1}</span></label>', 'vp-inner-popup-condvalueastext','Text');
+            content.appendLine('</div>');
+            content.appendLine('</div>');
+
+            // tab 4. apply
             content.appendFormatLine('<div class="{0} {1}" style="display: none;">', 'vp-inner-popup-tab', 'apply');
             content.appendLine('<div class="vp-grid-box">');
             content.appendLine('<div class="vp-grid-col-110">');
@@ -1228,7 +1433,7 @@ define([
         renderAddValueBox(idx) {
             // add dataselector
             let valueSelector = new DataSelector({
-                pageThis: this, id: 'vp_addValue' + idx, classes: 'vp-inner-popup-value', placeholder: 'Type value'
+                pageThis: this, classes: 'vp-inner-popup-value', placeholder: 'Type value', withPopup: false
             });
             $(this.wrapSelector('.vp-inner-popup-body')).find('.vp-inner-popup-value:nth(' + idx + ')').replaceWith(valueSelector.toTagString());
         }
@@ -1507,8 +1712,9 @@ define([
             content.appendLine('<div>');
             content.appendLine('<table class="vp-tbl-gap5 wp100"><colgroup><col width="110px"><col width="*"></colgroup>');
             content.appendFormatLine('<tr><th class="{0}">{1}</th>', '', 'Column');
-            var target = this.state.selected.map(col => col.label).join(',');
-            content.appendFormatLine('<td><input type="text" class="{0}" value="{1}" readonly/>', 'vp-inner-popup-input1', target);
+            var target = this.state.selected[0];
+            content.appendFormatLine('<td><input type="text" class="{0}" value="{1}" data-code="{2}" readonly/>'
+                , 'vp-inner-popup-input1', target.label, target.code);
             content.appendLine('</td></tr>');
             content.appendLine('<tr><th><label>Replace type</label></th>');
             content.appendFormatLine('<td><select class="{0}">', 'vp-inner-popup-replacetype');
@@ -1536,20 +1742,31 @@ define([
             content.appendLine('</div>');
             // replace page - 2. condition
             content.appendFormatLine('<div class="{0}" style="display:none;">', 'vp-inner-popup-tab condition');
-            // subset
-            content.appendLine('<table class="vp-tbl-gap5"><colgroup><col width="110px"><col width="*"></colgroup>');
-
-            content.appendLine('<tr><td><label>Condition</label></td>');
-            content.appendLine('<td><div class="vp-fr-subset-box">');
-            content.appendLine('<textarea class="vp-input vp-inner-popup-subset"></textarea>');
-            content.appendLine('</div></td>');
-            content.appendLine('</tr>');
-
-            content.appendLine('<tr><th><label>Variable</label></th>');
-            content.appendFormatLine('<td><input type="text" class="{0}"/>', 'vp-inner-popup-input3');
-            content.appendFormatLine('<label><input type="checkbox" class="{0}"/><span>{1}</span></label>', 'vp-inner-popup-istext3','Text');
-            content.appendLine('</td></tr>');
-            content.appendLine('</table></div>');
+            // condition table
+            content.appendLine('<table class="vp-inner-popup-condition-tbl vp-tbl-gap5 wp100"><colgroup><col width="110px"><col width="*"></colgroup>');
+            // content.appendLine('<tr><td><label>Condition</label></td>');
+            // content.appendLine('<td><div class="vp-fr-subset-box">');
+            // content.appendLine('<textarea class="vp-input vp-inner-popup-subset"></textarea>');
+            // content.appendLine('</div></td>');
+            // content.appendLine('</tr>');
+
+            // content.appendLine('<tr><th><label>Variable</label></th>');
+            // content.appendFormatLine('<td><input type="text" class="{0}"/>', 'vp-inner-popup-input3');
+            // content.appendFormatLine('<label><input type="checkbox" class="{0}"/><span>{1}</span></label>', 'vp-inner-popup-istext3','Text');
+            // content.appendLine('</td></tr>');
+            content.appendLine(this.renderReplaceCondition());
+            content.appendFormatLine('<tr><td colspan="4"><button type="button" class="vp-button {0}">{1}</button></td></tr>',
+                'vp-inner-popup-add-cond', '+ Condition');
+            content.appendLine('</table>');
+            content.appendLine('<hr style="margin: 5px 0;"/>');
+            // value
+            content.appendLine('<div class="vp-grid-col-95">');
+            content.appendLine('<label>Value</label>');
+            content.appendLine('<div>')
+            content.appendFormatLine('<input type="text" class="{0}"/>', 'vp-inner-popup-condvalue');
+            content.appendFormatLine('<label><input type="checkbox" class="{0}"/><span>{1}</span></label>', 'vp-inner-popup-condvalueastext','Text');
+            content.appendLine('</div>');
+            content.appendLine('</div>');
 
             content.appendLine('</div>'); // end of vp-inner-popup-addpage
 
@@ -1576,6 +1793,86 @@ define([
             return content.toString();
         }
 
+        handleConditionAdd() {
+            var conditonBox = $(this.renderReplaceCondition());
+
+            // hide last connect operator
+            conditonBox.find('.vp-inner-popup-oper-connect').hide();
+
+            // show connect operator right before last one
+            $(this.wrapSelector('.vp-inner-popup-oper-connect:last')).show();
+            conditonBox.insertBefore(this.wrapSelector('.vp-inner-popup-condition-tbl tr:last'));
+        }
+
+        renderReplaceCondition() {
+            var content = new com_String();
+            content.appendLine('<tr><td>');
+            // delete condition button
+            content.appendLine('<div class="vp-icon-btn vp-icon-close-small vp-inner-popup-del-cond" style="float:right;"></div>');
+            // condition line
+            content.appendLine('<div class="vp-td-line mb5">');
+            // - 1. column list
+            content.appendFormatLine('<select class="{0} {1}">', 'vp-select m', 'vp-inner-popup-col-list');
+            // add .index
+            content.appendFormatLine('<option data-code="{0}" value="{1}">{2}</option>', '.index', '.index', 'index');
+            this.state.columnList.forEach(col => {
+                content.appendFormatLine('<option data-code="{0}" data-dtype="{1}" value="{2}">{3}</option>',
+                    col.code, col.type, col.code, col.label);
+            });
+            content.appendLine('</select>');
+            // - 2. operator
+            content.appendLine(this.templateForConditionOperator(''));
+            // - 3. oper-value
+            content.appendLine('<input class="vp-input m vp-inner-popup-condition" type="text" placeholder="Value"/>');
+            content.appendLine('</div>');
+            content.appendLine('<div class="vp-td-line">');
+            content.appendLine('<select class="vp-select s vp-inner-popup-oper-connect" style="display:none;">');
+            content.appendLine('<option value="&">and</option>');
+            content.appendLine('<option value="|">or</option>');
+            content.appendLine('</select>');
+            // use text
+            content.appendFormatLine('<label class="{0}"><input type="checkbox" class="{1}" title="{2}"/><span>{3}</span></label>',
+                'vp-inner-popup-condition-use-text', 'vp-inner-popup-cond-use-text', 'Uncheck it if you want to use variable or numeric values.', 'Text');
+                content.appendLine('</div>');
+            content.appendLine('</td></tr>');
+            return content.toString();
+        }
+
+        templateForConditionOperator(dtype='object') {
+            var content = new com_String();
+            content.appendFormatLine('<select class="{0} {1}">', 'vp-select s', 'vp-inner-popup-oper-list');
+            var operList = ['', '==', '!=', '<', '<=', '>', '>=', 'contains', 'not contains', 'starts with', 'ends with', 'isnull()', 'notnull()'];
+            if (dtype == '') {
+                // .index
+                operList = ['', '==', '!=', '<', '<=', '>', '>='];
+            } else if (dtype != 'object') {
+                operList = ['', '==', '!=', '<', '<=', '>', '>=', 'isnull()', 'notnull()'];
+            }
+            operList.forEach(oper => {
+                content.appendFormatLine('<option value="{0}">{1}</option>', oper, oper);
+            });
+            content.appendLine('</select>');
+            return content.toString();
+        }
+
+        templateForConditionCondInput(category, dtype='object') {
+            var vpCondSuggest = new SuggestInput();
+            vpCondSuggest.addClass('vp-input m vp-inner-popup-condition');
+
+            if (category && category.length > 0) {
+                vpCondSuggest.setPlaceholder((dtype=='object'?'Categorical':dtype) + " dtype");
+                vpCondSuggest.setSuggestList(function () { return category; });
+                vpCondSuggest.setSelectEvent(function (value) {
+                    $(this.wrapSelector()).val(value);
+                    $(this.wrapSelector()).trigger('change');
+                });
+                vpCondSuggest.setNormalFilter(false);
+            } else {
+                vpCondSuggest.setPlaceholder(dtype==''?'Value':(dtype + " dtype"));
+            }
+            return vpCondSuggest.toTagString();
+        }
+
         renderAsType() {
             var astypeList = this.astypeList;
             var content = new com_String();
@@ -1585,7 +1882,12 @@ define([
             content.appendFormatLine('<thead style="height: 30px"><th>{0}</th><th>{1}</th><th class="{2}">{3}</th></thead>'
                                     , 'Column', 'Data type', 'vp-orange-text', 'New data type');
             content.appendLine('<tbody>');
-            this.state.selected.forEach((col, idx) => {
+            let selectedList = this.state.selected;
+            if (selectedList.length === 0) {
+                // select all
+                selectedList = this.state.columnList;
+            }
+            selectedList.forEach((col, idx) => {
                 content.appendLine('<tr>');
                 content.appendFormatLine('<td title="{0}">{1}</td>', col.label, col.label);
                 content.appendFormatLine('<td><input type="text" value="{0}" readonly/></td>', col.type);
@@ -1616,6 +1918,12 @@ define([
             content.appendFormatLine('<option value="{0}">{1}</option>', "value", "Value");
             content.appendFormatLine('<option value="{0}">{1}</option>', "ffill", "Forward fill");
             content.appendFormatLine('<option value="{0}">{1}</option>', "bfill", "Back fill");
+            let statsMethodList = ['mean', 'median', 'mode', 'min', 'max'];
+            content.appendLine('<optgroup label="Statistics">');
+            statsMethodList.forEach(opt => {
+                content.appendFormatLine('<option value="{0}">{1}</option>', opt, opt);
+            })
+            content.appendLine('</optgroup>');
             content.appendLine('</select></td>');
             content.appendLine('</tr>');
             content.appendFormatLine('<tr class="{0}">', 'vp-inner-popup-value-row');
@@ -1649,10 +1957,10 @@ define([
                 <select class="vp-inner-popup-how">
                     <option value="">Select option...</option>
                     <option value="any">Any</option>
-                    <option value="all">All</option>
+                    <option value="all" selected>All</option>
                 </select>
                 <label>Threshold</label>
-                <input type="number" class="vp-input vp-inner-popup-thresh" value=""/>
+                <input type="number" class="vp-input vp-inner-popup-thresh" value="" disabled />
                 <label>Ignore index</label>
                 <select class="vp-inner-popup-ignoreindex">
                     <option value="">Select option...</option>
@@ -1689,6 +1997,26 @@ define([
             return content.toString();
         }
 
+        renderFillOutPage() {
+            let content = `<div class="vp-inner-popup-fillout-page vp-grid-col-110">
+                <label>Fill type</label>
+                <select class="vp-inner-popup-filltype">
+                    <option value="iqr">IQR</option>
+                    <option value="mean">Mean</option>
+                    <option value="median">Median</option>
+                    <option value="value">Value</option>
+                    <option value="NA">NA</option>
+                </select>
+                <label>Value</label>
+                <input type="text" class="vp-input vp-inner-popup-fillvalue" disabled>
+            </div>`;
+
+            // set content
+            $(this.wrapSelector('.vp-inner-popup-body')).html(content);
+            return content.toString();
+        }
+
+
         renderDropOutPage() {
             var content = new com_String();
             content.appendFormatLine('<div class="{0} vp-grid-box vp-center">', 'vp-inner-popup-dropout-page');
@@ -1707,7 +2035,7 @@ define([
             let size = { width: width, height: height };
             let that = this;
 
-            $(this.wrapSelector('.vp-inner-popup-body')).html('');
+            $(this.wrapSelector('.vp-inner-popup-body')).empty();
     
             switch (parseInt(type)) {
                 case FRAME_EDIT_TYPE.ADD_COL:
@@ -1719,15 +2047,33 @@ define([
                     // bind event for adding values to calculate
                     $(this.wrapSelector('.vp-inner-popup-addvalue')).on('click', function() {
                         let valueCount = $(that.wrapSelector('.vp-inner-popup-value')).length;
+                        let columnOptStr = '';
+                        that.state.columnList.forEach(col => {
+                            columnOptStr += `<option data-code="${col.code}" data-dtype="${col.type}" value="${col.code}">${col.label}</option>`;
+                        });
                         $(`<tr class="vp-inner-popup-oper-row">
                             <td></td>
                             <td>${that.renderCalculator(valueCount)}</td>
                         </tr>
                         <tr class="vp-inner-popup-value-row">
-                            <th><label>Variable</label></th>
+                            <th>
+                                <select class="vp-inner-popup-vartype">
+                                    <option value="variable">Variable</option>
+                                    <option value="column">Column</option>
+                                </select>
+                            </th>
+                            <td>
+                                <div class="vp-inner-popup-vartype-box variable">
+                                    <input type="text" class="vp-inner-popup-value"/>
+                                    <label><input type="checkbox" class="vp-inner-popup-istext"/><span>Text</span></label>
+                                </div>
+                                <div class="vp-inner-popup-vartype-box column" style="display:none;">
+                                    <select class="vp-select vp-inner-popup-var-col-list">
+                                        ${columnOptStr}
+                                    </select>
+                                </div>
+                            </td>
                             <td>
-                                <input type="text" class="vp-inner-popup-value"/>
-                                <label><input type="checkbox" class="vp-inner-popup-istext"/><span>Text</span></label>
                                 <span class="vp-inner-popup-delete-value vp-icon-close-small"></span>
                             </td>
                         </tr>`).insertBefore($(that.wrapSelector('.vp-inner-popup-addvalue-row')));
@@ -1844,55 +2190,53 @@ define([
                     break;
                 case FRAME_EDIT_TYPE.REPLACE:
                     title = 'Replace';
-                    // content = this.renderReplacePage();
                     content = this.renderReplacePage();
 
-                    // bind codemirror
-                    this.subsetCm = this.initCodemirror({ 
-                        key: 'vp-inner-popup-subset', 
-                        selector: this.wrapSelector('.vp-inner-popup-subset'), 
-                        type: 'readonly' 
-                    });
-                    // set subset
-                    let contentState = that.getPopupContent(type);
-                    this.subsetEditor = new Subset({ 
-                        pandasObject: this.state.tempObj,
-                        selectedColumns: [ com_util.convertToStr(contentState.name, contentState.nameastext) ],
-                        config: { name: 'Subset' } }, 
-                    { 
-                        useInputVariable: true,
-                        useInputColumns: true,
-                        targetSelector: this.wrapSelector('.vp-inner-popup-subset'),
-                        pageThis: this,
-                        allowSubsetTypes: ['iloc', 'loc'],
-                        beforeOpen: function(subsetThis) {
-                            let contentState = that.getPopupContent(type);
-                            let name = com_util.convertToStr(contentState.name, contentState.nameastext);
-                            subsetThis.state.selectedColumns = [ name ];
-                        },
-                        finish: function(code) {
-                            that.subsetCm.setValue(code);
-                            that.subsetCm.save();
-                            setTimeout(function () {
-                                that.subsetCm.refresh();
-                            }, 1);
-                        }
-                    });
+                    // // bind codemirror
+                    // this.subsetCm = this.initCodemirror({ 
+                    //     key: 'vp-inner-popup-subset', 
+                    //     selector: this.wrapSelector('.vp-inner-popup-subset'), 
+                    //     type: 'readonly' 
+                    // });
+                    // // set subset
+                    // let contentState = that.getPopupContent(type);
+                    // this.subsetEditor = new Subset({ 
+                    //     pandasObject: this.state.tempObj,
+                    //     selectedColumns: [ com_util.convertToStr(contentState.name, contentState.nameastext) ],
+                    //     config: { name: 'Subset' } }, 
+                    //     { 
+                    //         useInputVariable: true,
+                    //         useInputColumns: true,
+                    //         targetSelector: this.wrapSelector('.vp-inner-popup-subset'),
+                    //         pageThis: this,
+                    //         allowSubsetTypes: ['iloc', 'loc'],
+                    //         beforeOpen: function(subsetThis) {
+                    //             let contentState = that.getPopupContent(type);
+                    //             let name = com_util.convertToStr(contentState.name, contentState.nameastext);
+                    //             subsetThis.state.selectedColumns = [ name ];
+                    //         },
+                    //         finish: function(code) {
+                    //             that.subsetCm.setValue(code);
+                    //             that.subsetCm.save();
+                    //             setTimeout(function () {
+                    //                 that.subsetCm.refresh();
+                    //             }, 1);
+                    //         }
+                    //     });
                     // initial code
-                    var code = this.subsetEditor.generateCode();
-                    this.subsetCm.setValue(code);
-                    this.subsetCm.save();
-                    setTimeout(function () {
-                        that.subsetCm.refresh();
-                    }, 1);
-
-                    // data selector
-                    // vp-inner-popup-input3
+                    // var code = this.subsetEditor.generateCode();
+                    // this.subsetCm.setValue(code);
+                    // this.subsetCm.save();
+                    // setTimeout(function () {
+                    //     that.subsetCm.refresh();
+                    // }, 1);
+
                     // set dataselector
                     let replaceVarSelector = new DataSelector({
-                        pageThis: this, id: 'vp_replaceVariable', classes: 'vp-inner-popup-input3', placeholder: 'Type or select variable'
+                        pageThis: this, id: 'vp_replaceVariable', classes: 'vp-inner-popup-value', placeholder: 'Type or select variable'
+                        , withPopup: false
                     });
-                    $(this.wrapSelector('.vp-inner-popup-body')).find('.vp-inner-popup-input3').replaceWith(replaceVarSelector.toTagString());
+                    $(this.wrapSelector('.vp-inner-popup-body')).find('.vp-inner-popup-value').replaceWith(replaceVarSelector.toTagString());
                     break;
                 case FRAME_EDIT_TYPE.AS_TYPE:
                     title = 'Convert type';
@@ -1904,23 +2248,10 @@ define([
 
                     // set dataselector
                     let valueSelector = new DataSelector({
-                        pageThis: this, id: 'vp_fillValue', classes: 'vp-inner-popup-value', placeholder: 'Type or select value'
+                        pageThis: this, classes: 'vp-inner-popup-value', placeholder: 'Type or select value',
+                        withPopup: false
                     });
                     $(this.wrapSelector('.vp-inner-popup-body')).find('.vp-inner-popup-value').replaceWith(valueSelector.toTagString());
-
-                    // bind event on method
-                    $(this.wrapSelector('.vp-inner-popup-method')).on('change', function() {
-                        let changedVal = $(this).val();
-                        if (changedVal === 'value') {
-                            // show value row
-                            $(that.wrapSelector('.vp-inner-popup-value-row')).show();
-                            $(that.wrapSelector('.vp-inner-popup-fill-row')).hide();
-                        } else {
-                            // show method fill row
-                            $(that.wrapSelector('.vp-inner-popup-value-row')).hide();
-                            $(that.wrapSelector('.vp-inner-popup-fill-row')).show();
-                        }
-                    });
                     break;
                 case FRAME_EDIT_TYPE.DROP_NA:
                     title = 'Drop NA';
@@ -1930,6 +2261,18 @@ define([
                     title = 'Drop duplicates';
                     content = this.renderDropDupPage();
                     break;
+                case FRAME_EDIT_TYPE.FILL_OUT:
+                    title = 'Fill outlier';
+                    content = this.renderFillOutPage();
+
+                    // set dataselector
+                    let fillvalueSelector = new DataSelector({
+                        pageThis: this, classes: 'vp-inner-popup-fillvalue', placeholder: 'Type or select value',
+                        withPopup: false
+                    });
+                    $(this.wrapSelector('.vp-inner-popup-body')).find('.vp-inner-popup-fillvalue').replaceWith(fillvalueSelector.toTagString());
+                    $(this.wrapSelector('.vp-inner-popup-body')).find('.vp-inner-popup-fillvalue').prop('disabled', true);
+                    break;
                 case FRAME_EDIT_TYPE.DROP_OUT:
                     title = 'Drop outlier';
                     size = { width: 400, height: 200 };
@@ -2012,8 +2355,15 @@ define([
                         let values = [];
                         let opers = [];
                         $(this.wrapSelector('.vp-inner-popup-tab.variable tr.vp-inner-popup-value-row')).each((idx, tag) => {
-                            let valueastext = $(tag).find('.vp-inner-popup-istext').prop('checked');
-                            values.push(com_util.convertToStr($(tag).find('.vp-inner-popup-value').val(), valueastext));
+                            let varType = $(tag).find('.vp-inner-popup-vartype').val();
+                            if (varType === 'variable') {
+                                let valueastext = $(tag).find('.vp-inner-popup-istext').prop('checked');
+                                values.push(com_util.convertToStr($(tag).find('.vp-inner-popup-value').val(), valueastext));
+                            } else {
+                                // columns
+                                let column = $(tag).find('.vp-inner-popup-var-col-list').val();
+                                values.push(com_util.formatString("{0}[{1}]", this.state.tempObj, column));
+                            }
                             let oper = $(that.wrapSelector('.vp-inner-popup-oper:nth(' + idx + ')')).val();
                             if (oper && oper !== '') {
                                 opers.push(oper);
@@ -2021,17 +2371,61 @@ define([
                         });
                         content['values'] = values;
                         content['opers'] = opers;
+                    } else if (tab == 'value') {
+                        content['target'] = $(this.wrapSelector('.vp-inner-popup-value-col-list option:selected')).data('code');
+                        var useregex = $(this.wrapSelector('.vp-inner-popup-use-regex')).prop('checked');
+                        content['useregex'] = useregex;
+                        content['list'] = [];
+                        for (var i=0; i <= this.state.popup.replace.index; i++) {
+                            var origin = $(this.wrapSelector('.vp-inner-popup-origin' + i)).val();
+                            var origintext = $(this.wrapSelector('.vp-inner-popup-origin-istext'+i)).prop('checked');
+                            var replace = $(this.wrapSelector('.vp-inner-popup-replace' + i)).val();
+                            var replacetext = $(this.wrapSelector('.vp-inner-popup-replace-istext'+i)).prop('checked');
+                            if (origin && replace) {
+                                content['list'].push({
+                                    origin: origin,
+                                    origintext: origintext,
+                                    replace: replace,
+                                    replacetext: replacetext
+                                });
+                            }
+                        }
                     } else if (tab == 'apply') {
                         content['column'] = $(this.wrapSelector('.vp-inner-popup-apply-column')).val();
                         content['apply'] = $(this.wrapSelector('.vp-inner-popup-apply-lambda')).val();
+                    } else if (tab === 'condition') {
+                        content['list'] = [];
+                        var colTag = $(this.wrapSelector('.vp-inner-popup-col-list'));
+                        var operTag = $(this.wrapSelector('.vp-inner-popup-oper-list'));
+                        var condTag = $(this.wrapSelector('.vp-inner-popup-condition'));
+                        var condTextTag = $(this.wrapSelector('.vp-inner-popup-cond-use-text'));
+                        var operConnTag = $(this.wrapSelector('.vp-inner-popup-oper-connect'));
+                        for (let i=0; i<colTag.length; i++) {
+                            var col = $(colTag[i]).val();
+                            var oper = $(operTag[i]).val();
+                            var cond = $(condTag[i]).val();
+                            var condText = $(condTextTag[i]).prop('checked');
+                            var operConn = $(operConnTag[i]).val();
+                            var condObj = {};
+                            if (col !== '' && oper !== '' && cond !== '') {
+                                condObj = {
+                                    colName: col,
+                                    oper: oper,
+                                    cond: cond,
+                                    condAsText: condText
+                                };
+                                if (i < (colTag.length - 1)) {
+                                    condObj['connector'] = operConn;
+                                }
+                                content['list'].push(condObj);
+                            }
+                        }
+                        content['value'] = $(this.wrapSelector('.vp-inner-popup-condvalue')).val();
+                        content['valueastext'] = $(this.wrapSelector('.vp-inner-popup-condvalueastext')).prop('checked');
                     }
                     break;
                 case FRAME_EDIT_TYPE.REPLACE:
-                    content['name'] = $(this.wrapSelector('.vp-inner-popup-input1')).val();
-                    if (content['name'] == '') {
-                        $(this.wrapSelector('.vp-inner-popup-input1')).attr({'placeholder': 'Required input'});
-                        $(this.wrapSelector('.vp-inner-popup-input1')).focus();
-                    }
+                    content['name'] = $(this.wrapSelector('.vp-inner-popup-input1')).data('code');
                     var tab = $(this.wrapSelector('.vp-inner-popup-replacetype')).val();
                     content['replacetype'] = tab;
                     if (tab == 'value') {
@@ -2053,9 +2447,34 @@ define([
                             }
                         }
                     } else if (tab === 'condition') {
-                        content['subset'] = this.subsetCm?this.subsetCm.getValue():'';
-                        content['value'] = $(this.wrapSelector('.vp-inner-popup-input3')).val();
-                        content['valueastext'] = $(this.wrapSelector('.vp-inner-popup-istext3')).prop('checked');
+                        content['list'] = [];
+                        var colTag = $(this.wrapSelector('.vp-inner-popup-col-list'));
+                        var operTag = $(this.wrapSelector('.vp-inner-popup-oper-list'));
+                        var condTag = $(this.wrapSelector('.vp-inner-popup-condition'));
+                        var condTextTag = $(this.wrapSelector('.vp-inner-popup-cond-use-text'));
+                        var operConnTag = $(this.wrapSelector('.vp-inner-popup-oper-connect'));
+                        for (let i=0; i<colTag.length; i++) {
+                            var col = $(colTag[i]).val();
+                            var oper = $(operTag[i]).val();
+                            var cond = $(condTag[i]).val();
+                            var condText = $(condTextTag[i]).prop('checked');
+                            var operConn = $(operConnTag[i]).val();
+                            var condObj = {};
+                            if (col !== '' && oper !== '' && cond !== '') {
+                                condObj = {
+                                    colName: col,
+                                    oper: oper,
+                                    cond: cond,
+                                    condAsText: condText
+                                };
+                                if (i < (colTag.length - 1)) {
+                                    condObj['connector'] = operConn;
+                                }
+                                content['list'].push(condObj);
+                            }
+                        }
+                        content['value'] = $(this.wrapSelector('.vp-inner-popup-condvalue')).val();
+                        content['valueastext'] = $(this.wrapSelector('.vp-inner-popup-condvalueastext')).prop('checked');
                     }
                     break;
                 case FRAME_EDIT_TYPE.RENAME:
@@ -2086,12 +2505,19 @@ define([
                     content['ascending'] = $(this.wrapSelector('.vp-inner-popup-isascending')).val();
                     break;
                 case FRAME_EDIT_TYPE.AS_TYPE:
-                    this.state.selected.forEach((col, idx) => {
+                    let selectedList = this.state.selected;
+                    if (selectedList.length === 0) {
+                        // select all
+                        selectedList = this.state.columnList;
+                    }
+                    selectedList.forEach((col, idx) => {
                         var value = $(this.wrapSelector('.vp-inner-popup-astype'+idx)).val();
-                        content[idx] = {
-                            label: col.code,
-                            value: value
-                        };
+                        if (value !== undefined && value !== '') {
+                            content[idx] = {
+                                label: col.code,
+                                value: value
+                            };
+                        }
                     });
                     break;
                 case FRAME_EDIT_TYPE.DISCRETIZE:
@@ -2141,6 +2567,10 @@ define([
                     content['thresh'] = $(this.wrapSelector('.vp-inner-popup-thresh')).val();
                     content['ignore_index'] = $(this.wrapSelector('.vp-inner-popup-ignoreindex')).val();
                     break;
+                case FRAME_EDIT_TYPE.FILL_OUT:
+                    content['filltype'] = $(this.wrapSelector('.vp-inner-popup-filltype')).val();
+                    content['fillvalue'] = $(this.wrapSelector('.vp-inner-popup-fillvalue')).val();
+                    break;
                 case FRAME_EDIT_TYPE.DROP_DUP:
                     content['keep'] = $(this.wrapSelector('.vp-inner-popup-how')).val();
                     content['ignore_index'] = $(this.wrapSelector('.vp-inner-popup-ignoreindex')).val();
@@ -2294,22 +2724,26 @@ define([
                     }
                     break;
                 case FRAME_EDIT_TYPE.DROP_NA:
-                    var locObj = '';
+                    var dropNAOptions = [];
                     if (axis == FRAME_AXIS.ROW) {
-                        code.appendFormat("{0}.loc[[{1}],:].dropna(axis=0", tempObj, selectedName);
+                        dropNAOptions.push("axis=1");
                     } else {
-                        code.appendFormat("{0}.loc[:,[{1}]].dropna(axis=1", tempObj, selectedName);
+                        dropNAOptions.push("axis=0");
+                    }
+                    if (selectedName && selectedName !== '') {
+                        dropNAOptions.push(com_util.formatString("subset=[{0}]", selectedName));
                     }
                     if (content.how && content.how !== '') {
-                        code.appendFormat(", how='{0}'", content.how);
+                        dropNAOptions.push(com_util.formatString("how='{0}'", content.how));
                     }
                     if (content.thresh && content.thresh !== '') {
-                        code.appendFormat(", thresh={0}", content.thresh);
+                        dropNAOptions.push(com_util.formatString("thresh={0}", content.thresh));
                     }
                     if (content.ignore_index && content.ignore_index !== '') {
-                        code.appendFormat(", ignore_index={0}", content.ignore_index);
+                        dropNAOptions.push(com_util.formatString("ignore_index={0}", content.ignore_index));
                     }
-                    code.append(", inplace=True)");
+                    dropNAOptions.push("inplace=True");
+                    code.appendFormat("{0}.dropna({1})", tempObj, dropNAOptions.join(', '));
                     break;
                 case FRAME_EDIT_TYPE.DROP_DUP:
                     let dropDupOptions = [];
@@ -2322,12 +2756,21 @@ define([
                     if (content.ignore_index && content.ignore_index !== '') {
                         dropDupOptions.push(com_util.formatString("ignore_index={0}", content.ignore_index));
                     }
-                    dropDupOptions.push(com_util.formatString("inplace=True"));
+                    dropDupOptions.push("inplace=True");
                     code.appendFormat("{0}.drop_duplicates({1})", tempObj, dropDupOptions.join(', '));
                     break;
+                case FRAME_EDIT_TYPE.FILL_OUT:
+                    if (axis == FRAME_AXIS.COLUMN) {
+                        code.appendFormat("{0} = vp_fill_outlier({1}, [{2}], fill_type='{3}'", tempObj, tempObj, selectedName, content.filltype);
+                        if (content.filltype === 'value') {
+                            code.appendFormat(", fill_value_lst={0}", content.fillvalue);
+                        }
+                        code.append(")");
+                    }
+                    break;
                 case FRAME_EDIT_TYPE.DROP_OUT:
                     if (axis == FRAME_AXIS.COLUMN) {
-                        code.appendFormat("{0} = vp_drop_outlier({1}, {2})", tempObj, tempObj, selectedName);
+                        code.appendFormat("{0} = vp_drop_outlier({1}, [{2}])", tempObj, tempObj, selectedName);
                     }
                     break;
                 case FRAME_EDIT_TYPE.LABEL_ENCODING:
@@ -2390,9 +2833,69 @@ define([
                             }
                             values.push(val);
                         });
-                        code.appendFormat("{0}[{1}] = {2}", tempObj, content.name, values.join(' '));
+                        let valueStr = values.join(' ');
+                        if (valueStr === "" || valueStr === "''") {
+                            code.appendFormat("{0}[{1}] = np.NaN", tempObj, content.name);
+                        } else {
+                            code.appendFormat("{0}[{1}] = {2}", tempObj, content.name, valueStr);
+                        }
+                    } else if (tab == 'value') {
+                        var replaceStr = new com_String();
+                        var targetName = content['target'];
+                        var useRegex = content['useregex'];
+                        content['list'].forEach((obj, idx) => {
+                            if (idx == 0) {
+                                replaceStr.appendFormat("{0}: {1}"
+                                                        , com_util.convertToStr(obj.origin, obj.origintext, useRegex)
+                                                        , com_util.convertToStr(obj.replace, obj.replacetext, useRegex));
+                            } else {
+                                replaceStr.appendFormat(", {0}: {1}"
+                                                        , com_util.convertToStr(obj.origin, obj.origintext, useRegex)
+                                                        , com_util.convertToStr(obj.replace, obj.replacetext, useRegex));
+                            }
+                        });
+                        code.appendFormat("{0}[{1}] = {2}[[{3}]].replace({{4}}", tempObj, content.name, tempObj, targetName, replaceStr);
+                        if (useRegex) {
+                            code.append(', regex=True');
+                        }
+                        code.append(')');
                     } else if (tab == 'apply') {
                         code.appendFormat("{0}[{1}] = {2}[{3}].apply({4})", tempObj, content.name, tempObj, content.column, content.apply);
+                    } else if (tab === 'condition') {
+                        code.appendFormat("{0}.loc[", tempObj);
+                        content['list'].forEach((obj, idx) => {
+                            let { colName, oper, cond, condAsText, connector } = obj;
+                            code.append('(');
+
+                            let colValue = tempObj;
+                            if (colName && colName != '') {
+                                if (colName == '.index') {
+                                    colValue += colName;
+                                } else {
+                                    colValue += com_util.formatString('[{0}]', colName);
+                                }
+                            }
+                            let condValue = com_util.convertToStr(cond, condAsText);
+                            if (oper == 'contains') {
+                                code.appendFormat('{0}.str.contains({1})', colValue, condValue);
+                            } else if (oper == 'not contains') {
+                                code.appendFormat('~{0}.str.contains({1})', colValue, condValue);
+                            } else if (oper == 'starts with') {
+                                code.appendFormat('{0}.str.startswith({1})', colValue, condValue);
+                            } else if (oper == 'ends with') {
+                                code.appendFormat('{0}.str.endswith({1})', colValue, condValue);
+                            } else if (oper == 'isnull()' || oper == 'notnull()') {
+                                code.appendFormat('{0}.{1}', colValue, oper);
+                            } else {
+                                code.appendFormat('{0}{1}{2}', colValue, oper != ''?(' ' + oper):'', condValue != ''?(' ' + condValue):'');
+                            }
+                            code.append(')');
+                            if (idx < (content['list'].length - 1)) {
+                                code.append(connector);
+                            }
+                        });
+                        var value = com_util.convertToStr(content.value, content.valueastext);
+                        code.appendFormat(", {0}] = {1}", content.name, value);
                     }
                     break;
                 case FRAME_EDIT_TYPE.ADD_ROW: 
@@ -2411,12 +2914,7 @@ define([
                     code.appendFormat("{0}.loc[{1}] = {2}", tempObj, content.name, values.join(' '));
                     break;
                 case FRAME_EDIT_TYPE.REPLACE:
-                    // if no name entered
-                    if (content.name == '') {
-                        return '';
-                    }
-                    var name = com_util.convertToStr(content.name, content.nameastext);
-                    name = selectedName;
+                    var name = content.name;
                     var tab = content.replacetype;
                     if (tab === 'value') {
                         var replaceStr = new com_String();
@@ -2441,8 +2939,40 @@ define([
                         }
                         code.append(')');
                     } else if (tab === 'condition') {
+                        code.appendFormat("{0}.loc[", tempObj);
+                        content['list'].forEach((obj, idx) => {
+                            let { colName, oper, cond, condAsText, connector } = obj;
+                            code.append('(');
+
+                            let colValue = tempObj;
+                            if (colName && colName != '') {
+                                if (colName == '.index') {
+                                    colValue += colName;
+                                } else {
+                                    colValue += com_util.formatString('[{0}]', colName);
+                                }
+                            }
+                            let condValue = com_util.convertToStr(cond, condAsText);
+                            if (oper == 'contains') {
+                                code.appendFormat('{0}.str.contains({1})', colValue, condValue);
+                            } else if (oper == 'not contains') {
+                                code.appendFormat('~{0}.str.contains({1})', colValue, condValue);
+                            } else if (oper == 'starts with') {
+                                code.appendFormat('{0}.str.startswith({1})', colValue, condValue);
+                            } else if (oper == 'ends with') {
+                                code.appendFormat('{0}.str.endswith({1})', colValue, condValue);
+                            } else if (oper == 'isnull()' || oper == 'notnull()') {
+                                code.appendFormat('{0}.{1}', colValue, oper);
+                            } else {
+                                code.appendFormat('{0}{1}{2}', colValue, oper != ''?(' ' + oper):'', condValue != ''?(' ' + condValue):'');
+                            }
+                            code.append(')');
+                            if (idx < (content['list'].length - 1)) {
+                                code.append(connector);
+                            }
+                        });
                         var value = com_util.convertToStr(content.value, content.valueastext);
-                        code.appendFormat("{0} = {1}", content.subset, value);
+                        code.appendFormat(", {0}] = {1}", content.name, value);
                     }
                     break;
                 case FRAME_EDIT_TYPE.AS_TYPE:
@@ -2493,11 +3023,13 @@ define([
                     code.appendFormat("{0} = {1}.fillna(", subsetObjStr, subsetObjStr);
                     if (content['method'] == 'value') {
                         code.append(com_util.convertToStr(content['value'], content['valueastext']));
-                    } else {
+                    } else if (content['method'] === 'ffill' || content['method'] === 'bfill') {
                         code.appendFormat("method='{0}'", content['method']);
                         if (content['limit'] && content['limit'] !== '') {
                             code.appendFormat(", limit={0}", content['limit']);
                         }
+                    } else {
+                        code.appendFormat("{0}.{1}()", subsetObjStr, content['method']);
                     }
                     code.append(')');
                     break;
@@ -2587,9 +3119,9 @@ define([
                                         // add column
                                         // LAB: img to url
                                         // table.appendFormatLine('<th class="{0}"><img src="{1}"/></th>', VP_FE_ADD_COLUMN, com_Const.IMAGE_PATH + 'plus.svg');
-                                        // if (colLevIdx === 0) {
-                                        //     table.appendFormatLine('<th class="{0}"><div class="{1}"></div></th>', VP_FE_ADD_COLUMN, 'vp-icon-plus');
-                                        // }
+                                        if (colLevIdx === 0) {
+                                            table.appendFormatLine('<th class="{0}"><div class="{1}"></div></th>', VP_FE_ADD_COLUMN, 'vp-icon-plus');
+                                        }
                         
                                         table.appendLine('</tr>');
                                     }
@@ -2605,7 +3137,7 @@ define([
                                                                 , colCode, FRAME_AXIS.COLUMN, col.type, col.label, VP_FE_TABLE_COLUMN, colClass, col.label);
                                     });
                                     // // add column
-                                    // table.appendFormatLine('<th class="{0}"><div class="{1}"></div></th>', VP_FE_ADD_COLUMN, 'vp-icon-plus');
+                                    table.appendFormatLine('<th class="{0}"><div class="{1}"></div></th>', VP_FE_ADD_COLUMN, 'vp-icon-plus');
                     
                                     table.appendLine('</tr>');
                                 }
@@ -2698,6 +3230,11 @@ define([
                                 var replacedCode = codeStr.replaceAll(that.state.tempObj, that.state.returnObj);
                                 that.setPreview(replacedCode);
                             }
+
+                            // if add column, go to the right position
+                            if (that.state.popup.type === FRAME_EDIT_TYPE.ADD_COL) {
+                                $(that.wrapSelector('.vp-fe-add-column'))[0].scrollIntoView();
+                            }
                             
                             // if scrollPos is saved, go to the position
                             if (scrollPos >= 0) {
@@ -2823,7 +3360,8 @@ define([
 
         FILL_NA: 13,
         DROP_NA: 4,
-        DROP_OUT: 11, 
+        FILL_OUT: 18, 
+        DROP_OUT: 11,
         DROP_DUP: 5,
 
         SHOW: 99
diff --git a/visualpython/js/m_apps/Information.js b/visualpython/js/m_apps/Information.js
index 881a2e4a..a9a61bcf 100644
--- a/visualpython/js/m_apps/Information.js
+++ b/visualpython/js/m_apps/Information.js
@@ -82,7 +82,16 @@ define([
                             \n}, index=['Combination']+${data}.columns.to_list())\
                             \n_duplicated_df", dtype: ['DataFrame', 'Series'], toframe: true },
                         { id: 'unique', label: 'Unique', code: '${data}.unique()', dtype: ['Series'] },
-                        { id: 'value_counts', label: 'Value counts', code: '${data}.value_counts()', dtype: ['DataFrame', 'Series'] },
+                        { id: 'value_counts', label: 'Value counts', code: "_value_counts_dict = {}\
+                        \nfor col in ${data}.columns:\
+                        \n    if pd.api.types.is_numeric_dtype(${data}[col]):\
+                        \n        _value_counts = ${data}[col].value_counts(bins=10, sort=False)\
+                        \n        _value_counts_dict[(col, 'bins')] = list(_value_counts.index) + ['']*(10 - len(_value_counts))\
+                        \n    else:\
+                        \n        _value_counts = ${data}[col].value_counts()\
+                        \n        _value_counts_dict[(col, 'category')] = list(_value_counts.index) + ['']*(10 - len(_value_counts))\
+                        \n    _value_counts_dict[(col, 'count')] = list(_value_counts.values) + ['']*(10 - len(_value_counts))\
+                        \npd.DataFrame(_value_counts_dict)", dtype: ['DataFrame', 'Series'], toframe: true },
                     ]
                 },
                 {
@@ -90,17 +99,20 @@ define([
                     label: 'Statistics',
                     dtype: ['DataFrame', 'Series'],
                     child: [
+                        /** checkbox */
                         { id: 'count', label: 'count', code: '${data}.count()' },
-                        { id: 'min', label: 'min', code: '${data}.min()' },
-                        { id: 'max', label: 'max', code: '${data}.max()' },
+                        { id: 'min', label: 'min', code: '${data}.min(numeric_only=True)' },
+                        { id: 'max', label: 'max', code: '${data}.max(numeric_only=True)' },
                         { id: 'quantile', label: 'quantile', code: '${data}.quantile(numeric_only=True)' },
-                        { id: 'sum', label: 'sum', code: '${data}.sum()' },
+                        { id: 'sum', label: 'sum', code: '${data}.sum(numeric_only=True)' },
                         { id: 'mean', label: 'mean', code: '${data}.mean(numeric_only=True)' },
                         { id: 'median', label: 'median', code: '${data}.median(numeric_only=True)' },
                         // { id: 'mad', label: 'mad', code: '${data}.mad(numeric_only=True)' }, // FutureWarning: Deprecated and will be removed
                         { id: 'var', label: 'var', code: '${data}.var(numeric_only=True)' },
                         { id: 'std', label: 'std', code: '${data}.std(numeric_only=True)' },
                         { id: 'skew', label: 'skew', code: '${data}.skew(numeric_only=True)' },
+                        { id: 'kurtosis', label: 'kurtosis', code: '${data}.kurtosis(numeric_only=True)' },
+                        /** radio */
                         { id: 'cumsum', label: 'cumsum', code: '${data}.cumsum()', type: 'radio' },
                         { id: 'cummin', label: 'cummin', code: '${data}.cummin()', type: 'radio' },
                         { id: 'cummax', label: 'cummax', code: '${data}.cummax()', type: 'radio' },
@@ -549,7 +561,7 @@ define([
                                 let childObj = infoObj.child.find(obj=>obj.id === itemId);
                                 statList.push(com_util.formatString("'{0}': {1}", itemId, childObj.code));
                             });
-                            if (currentDtype === 'Series') {
+                            if (currentDtype === 'Series' && selected.length > 0) {
                                 // if multiple stats selected, set series data as dataframe
                                 dataVar = new com_String();
                                 dataVar.appendFormat("{0}[[{1}]]", data, selected.map(col=>col.code).join(','));
@@ -557,6 +569,12 @@ define([
                             }
                             codePattern = com_util.formatString("pd.DataFrame({{0}})", statList.join(','));
                         } else {
+                            if (currentDtype === 'Series' && selected.length > 0) {
+                                // if multiple stats selected, set series data as dataframe
+                                dataVar = new com_String();
+                                dataVar.appendFormat("{0}[[{1}]]", data, selected.map(col=>col.code).join(','));
+                                currentDtype = 'DataFrame';
+                            }
                             let childObj = infoObj.child.find(obj=>obj.id === menuItem[0]);
                             codePattern = childObj.code;
                         }
@@ -567,10 +585,16 @@ define([
                     // only one method selected
                     if (menuItem.length > 0 && infoObj.child) {
                         let childObj = infoObj.child.find(obj=>obj.id === menuItem[0]);
-                        if (childObj.toframe === true && currentDtype === 'Series') {
-                            dataVar = new com_String();
-                            dataVar.appendFormat("{0}[[{1}]]", data, selected.map(col=>col.code).join(','));
-                            currentDtype = 'DataFrame';
+                        if (childObj.toframe === true) {
+                            if (dtype === 'Series') {
+                                dataVar = new com_String();
+                                dataVar.appendFormat("{0}.to_frame()", data);
+                                currentDtype = 'DataFrame';
+                            } else if (currentDtype === 'Series') {
+                                dataVar = new com_String();
+                                dataVar.appendFormat("{0}[[{1}]]", data, selected.map(col=>col.code).join(','));
+                                currentDtype = 'DataFrame';
+                            }
                         }
                         codePattern = childObj.code;
                     } else {
diff --git a/visualpython/js/m_apps/Snippets.js b/visualpython/js/m_apps/Snippets.js
index d0d76129..dc0a528a 100644
--- a/visualpython/js/m_apps/Snippets.js
+++ b/visualpython/js/m_apps/Snippets.js
@@ -412,7 +412,7 @@ define([
             // prevent occuring header click event by clicking input
             $(this.wrapSelector('.vp-sn-item-title')).off('click');
             $(this.wrapSelector('.vp-sn-item-title')).on('click', function(evt) {
-                evt.stopPropagation();
+                // evt.stopPropagation();
             });
 
             // item title save
diff --git a/visualpython/js/m_apps/Subset.js b/visualpython/js/m_apps/Subset.js
index a6272232..b71b129f 100644
--- a/visualpython/js/m_apps/Subset.js
+++ b/visualpython/js/m_apps/Subset.js
@@ -26,6 +26,20 @@ define([
 
     /**
      * Subset
+     * ====================================
+     * Special mode
+     * 1. useAsModule : Use subset as module like DataSelector
+     *      - No allocation
+     *      - No run to cell (able to use apply button instead)
+     *      - renders button to target
+     * 2. useInputVariable : Use subset as module but use applied variable
+     *      - No allocation
+     *      - No data selection
+     *      - No run to cell
+     *      - renders button to target
+     * 3. useInputColumns : Use subset as module but use applied columns
+     *      - No allocation
+     *      - No column selection
      */
     class Subset extends PopupComponent {
         _init() {
@@ -39,8 +53,9 @@ define([
             this.targetSelector = this.prop.targetSelector;
             this.pageThis = this.prop.pageThis;
 
+            this.useAsModule = this.prop.useAsModule;
             this.useInputVariable = this.prop.useInputVariable;
-            if (this.useInputVariable) {
+            if (this.useInputVariable === true || this.useAsModule === true) {
                 this.eventTarget = this.targetSelector;
                 this.useCell = false; // show apply button only
             }
@@ -77,6 +92,7 @@ define([
                 useCopy: false,
                 toFrame: false,
                 subsetType: 'loc', // subset / loc / iloc / query
+                returnType: '',
 
                 rowType: 'condition',
                 rowList: [],
@@ -115,6 +131,13 @@ define([
             this.loadStateAfterRender();
             
             // render button
+            if (this.useAsModule) {
+                // render button
+                this.renderButton();
+
+                // hide allocate to
+                $(this.wrapSelector('.' + VP_DS_ALLOCATE_TO)).closest('tr').hide();
+            }
             if (this.useInputVariable) {
                 // set readonly
                 $(this.wrapSelector('.' + VP_DS_PANDAS_OBJECT)).attr('disabled', true);
@@ -175,7 +198,7 @@ define([
             buttonTag.appendFormat('<button type="button" class="{0} {1} {2}">{3}</button>',
                 VP_DS_BTN, this.uuid, 'vp-button', 'Subset');
             if (this.pageThis) {
-                $(this.targetSelector).parent().append(buttonTag.toString());
+                $(buttonTag.toString()).insertAfter($(this.targetSelector));
             }
         }
         renderSubsetType(dataType) {
@@ -563,6 +586,7 @@ define([
         renderColumnConditionList(colList) {
             var tag = new com_String();
             tag.appendFormatLine('<table class="{0}">', VP_DS_CONDITION_TBL);
+            tag.appendLine(this.templateForConditionBox(colList));
             tag.appendLine('<tr>');
             tag.appendFormatLine('<td colspan="4"><button type="button" class="{0} {1}">{2}</button></td>',
                 VP_DS_BUTTON_ADD_CONDITION, 'vp-add-col', '+ Condition');
@@ -743,6 +767,7 @@ define([
                         var varType = JSON.parse(result);
                         that.state.pandasObject = prevValue;
                         that.state.dataType = varType;
+                        that.state.returnType = varType;
                         $(that.wrapSelector('.' + VP_DS_PANDAS_OBJECT_BOX)).replaceWith(function () {
                             return $(com_util.formatString('<div style="display:inline-block"><input class="{0} {1}" value="{2}" disabled /></div>',
                                 'vp-input', VP_DS_PANDAS_OBJECT, prevValue));
@@ -783,6 +808,7 @@ define([
                         $(this.wrapSelector()).data('dtype', item.dtype);
                         that.state.pandasObject = value;
                         that.state.dataType = item.dtype;
+                        that.state.returnType = item.dtype;
                         $(this.wrapSelector()).trigger('change');
                     });
                     variableInput.setNormalFilter(true);
@@ -1161,6 +1187,7 @@ define([
 
                 that.state.pandasObject = varName;
                 that.state.dataType = event.dataType ? event.dataType : that.state.dataType;
+                that.state.returnType = that.state.dataType;
                 that.state.rowList = [];
                 that.state.rowLimit = 10;
                 that.state.columnList = [];
@@ -1813,10 +1840,14 @@ define([
                 if (this.state.colType == 'indexing') {
                     if (this.useInputColumns == true) {
                         colList = this.state.selectedColumns;
-                        if (colList.length == 1) {
-                            colSelection.appendFormat('{0}', colList.toString());
-                        } else {
-                            colSelection.appendFormat('[{0}]', colList.toString());
+                        if (colList.length > 0) {
+                            if (colList.length == 1) {
+                                colSelection.appendFormat('{0}', colList.toString());
+                                this.state.returnType = 'Series';
+                            } else {
+                                colSelection.appendFormat('[{0}]', colList.toString());
+                                this.state.returnType = 'DataFrame';
+                            }
                         }
                     } else {
                         var colTags = $(this.wrapSelector('.' + VP_DS_SELECT_ITEM + '.select-col.added:not(.moving)'));
@@ -1836,11 +1867,14 @@ define([
                                 // to frame
                                 if (this.state.toFrame) {
                                     colSelection.appendFormat('[{0}]', colList.toString());
+                                    this.state.returnType = 'DataFrame';
                                 } else {
                                     colSelection.appendFormat('{0}', colList.toString());
+                                    this.state.returnType = 'Series';
                                 }
                             } else {
                                 colSelection.appendFormat('[{0}]', colList.toString());
+                                this.state.returnType = 'DataFrame';
                             }
     
                         } else {
@@ -1855,31 +1889,34 @@ define([
             }
 
             // use simple selection
-            if (this.state.subsetType == 'subset') {
-                if (rowSelection.toString() != ':' && rowSelection.toString() != '') {
-                    code.appendFormat('[{0}]', rowSelection.toString());
-                }
-                if (colSelection.toString() != ':' && colSelection.toString() != '') {
-                    code.appendFormat('[{0}]', colSelection.toString());
-                }
-            } else if (this.state.subsetType == 'loc') {
-                if (this.state.dataType == 'DataFrame') {
-                    code.appendFormat('.loc[{0}, {1}]', rowSelection.toString(), colSelection.toString());
-                } else {
-                    code.appendFormat('.loc[{0}]', rowSelection.toString());
-                }
-            } else if (this.state.subsetType == 'iloc') {
-                if (this.state.dataType == 'DataFrame') {
-                    code.appendFormat('.iloc[{0}, {1}]', rowSelection.toString(), colSelection.toString());
-                } else {
-                    code.appendFormat('.iloc[{0}]', rowSelection.toString());
-                }
-            } else if (this.state.subsetType == 'query') {
-                if (rowSelection.toString() != ':' && rowSelection.toString() != '') {
-                    code.appendFormat('.query("{0}")', rowSelection.toString());
-                }
-                if (colSelection.toString() != ':' && colSelection.toString() != '') {
-                    code.appendFormat('[{0}]', colSelection.toString());
+            if ((rowSelection.toString() !== ':' && rowSelection.toString() !== '')
+                || (colSelection.toString() !== ':' && colSelection.toString() !== '')) {
+                if (this.state.subsetType == 'subset') {
+                    if (rowSelection.toString() != ':' && rowSelection.toString() != '') {
+                        code.appendFormat('[{0}]', rowSelection.toString());
+                    }
+                    if (colSelection.toString() != ':' && colSelection.toString() != '') {
+                        code.appendFormat('[{0}]', colSelection.toString());
+                    }
+                } else if (this.state.subsetType == 'loc') {
+                    if (this.state.dataType == 'DataFrame') {
+                        code.appendFormat('.loc[{0}, {1}]', rowSelection.toString(), colSelection.toString());
+                    } else {
+                        code.appendFormat('.loc[{0}]', rowSelection.toString());
+                    }
+                } else if (this.state.subsetType == 'iloc') {
+                    if (this.state.dataType == 'DataFrame') {
+                        code.appendFormat('.iloc[{0}, {1}]', rowSelection.toString(), colSelection.toString());
+                    } else {
+                        code.appendFormat('.iloc[{0}]', rowSelection.toString());
+                    }
+                } else if (this.state.subsetType == 'query') {
+                    if (rowSelection.toString() != ':' && rowSelection.toString() != '') {
+                        code.appendFormat('.query("{0}")', rowSelection.toString());
+                    }
+                    if (colSelection.toString() != ':' && colSelection.toString() != '') {
+                        code.appendFormat('[{0}]', colSelection.toString());
+                    }
                 }
             }
 
@@ -1911,9 +1948,11 @@ define([
             if (this.useInputVariable) {
                 this.loadVariables();
                 this.reloadSubsetData();
+            }        
+            if (this.useCell === false) {
                 // show save button only
                 this.setSaveOnlyMode();
-            }        
+            }
             // generate code after displaying page
             // - codemirror can be set after display    
             this.generateCode();
@@ -1924,13 +1963,13 @@ define([
         //====================================================================
 
         hideButton() {
-            if (this.useInputVariable) {
+            if (this.useInputVariable === true || this.useAsModule === true) {
                 $(this.pageThis.wrapSelector('.' + VP_DS_BTN + '.' + this.uuid)).hide();
             }
         }
 
         disableButton() {
-            if (this.useInputVariable) {
+            if (this.useInputVariable === true || this.useAsModule === true) {
                 var buttonEle = $(this.pageThis.wrapSelector('.' + VP_DS_BTN + '.' + this.uuid));
                 if (!buttonEle.hasClass('disabled')) {
                     buttonEle.addClass('disabled');
@@ -1939,12 +1978,12 @@ define([
         }
 
         enableButton() {
-            if (this.useInputVariable) {
+            if (this.useInputVariable === true || this.useAsModule === true) {
                 $(this.pageThis.wrapSelector('.' + VP_DS_BTN + '.' + this.uuid)).removeClass('disabled');
             }
         }
         showButton() {
-            if (this.useInputVariable) {
+            if (this.useInputVariable === true || this.useAsModule === true) {
                 $(this.pageThis.wrapSelector('.' + VP_DS_BTN + '.' + this.uuid)).show();
             }
         }
diff --git a/visualpython/js/m_library/m_pandas/readFile.js b/visualpython/js/m_library/m_pandas/readFile.js
index fafb85ba..296d3509 100644
--- a/visualpython/js/m_library/m_pandas/readFile.js
+++ b/visualpython/js/m_library/m_pandas/readFile.js
@@ -57,10 +57,10 @@ define([
             };
             this.fileState = {
                 fileTypeId: {
-                    'csv': 'pd004',
-                    'excel': 'pd123',
-                    'json': 'pd076',
-                    'pickle': 'pd079'
+                    'csv': 'pd_readCsv',
+                    'excel': 'pd_readExcel',
+                    'json': 'pd_readJson',
+                    'pickle': 'pd_readPickle'
                 },
                 selectedType: 'csv',
                 package: null
diff --git a/visualpython/js/m_library/m_pandas/toFile.js b/visualpython/js/m_library/m_pandas/toFile.js
index a19dc303..41a58ab3 100644
--- a/visualpython/js/m_library/m_pandas/toFile.js
+++ b/visualpython/js/m_library/m_pandas/toFile.js
@@ -57,10 +57,10 @@ define([
             };
             this.fileState = {
                 fileTypeId: {
-                    'csv': 'pd005',
-                    'excel': 'pd124',
-                    'json': 'pd077',
-                    'pickle': 'pd078'
+                    'csv': 'pd_toCsv',
+                    'excel': 'pd_toExcel',
+                    'json': 'pd_toJson',
+                    'pickle': 'pd_toPickle'
                 },
                 selectedType: 'csv',
                 package: null
diff --git a/visualpython/js/m_stats/Anova.js b/visualpython/js/m_stats/Anova.js
new file mode 100644
index 00000000..6f40ccd2
--- /dev/null
+++ b/visualpython/js/m_stats/Anova.js
@@ -0,0 +1,467 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : Anova.js
+ *    Author          : Black Logic
+ *    Note            : ANOVA
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 05. 24
+ *    Change Date     :
+ */
+
+//============================================================================
+// [CLASS] Anova
+//============================================================================
+define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/anova.html'),
+    'vp_base/js/com/com_util',
+    'vp_base/js/com/com_Const',
+    'vp_base/js/com/com_String',
+    'vp_base/js/com/com_generatorV2',
+    'vp_base/js/com/component/PopupComponent',
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/m_apps/Subset'
+], function(nmHTML, com_util, com_Const, com_String, com_generator, PopupComponent, DataSelector, Subset) {
+
+    /**
+     * Anova
+     */
+    class Anova extends PopupComponent {
+        _init() {
+            super._init();
+            /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd', 'np', 'vp_confidence_interval', 'vp_sem'];
+
+            this.state = {
+                testType: 'one-way',
+                data: '',
+                dataType: '',
+                depVar: '',
+                factor: '',
+                factorA: '',
+                factorB: '',
+                covariate: '',
+                sigLevel: 0.05,
+                // Post hoc analysis option
+                tukeyHSD: true,
+                tukey: false,
+                scheffe: false,
+                duncan: false,
+                bonferroni: false,
+                // Display option
+                statistics: true,
+                boxplot: true,
+                equalVariance: true,
+                interPlot: true,
+                ...this.state
+            };
+
+            this.columnBindList = ['depVar', 'factor', 'factorA', 'factorB', 'covariate'];
+
+            this.subsetEditor = {};
+        }
+
+        _bindEvent() {
+            super._bindEvent();
+            /** Implement binding events */
+            var that = this;
+
+            $(this.wrapSelector('#testType')).on('change', function() {
+                let testType = $(this).val();
+                that.state.testType = testType;
+
+                $(that.wrapSelector('.vp-st-option')).hide();
+                $(that.wrapSelector('.vp-st-option.' + testType)).show();
+            });
+
+            $(this.wrapSelector('#data')).on('change', function() {
+                if (that.state.dataType === 'Series') {
+                    // Series
+                    that.columnBindList.forEach(id => {
+                        $(that.wrapSelector('#' + id)).html('');
+                        $(that.wrapSelector('#' + id)).prop('disabled', true);
+                    });
+                } else {
+                    // DataFrame
+                    that.columnBindList.forEach(id => {
+                        $(that.wrapSelector('#' + id)).prop('disabled', false);
+                    });
+                    com_generator.vp_bindColumnSource(that, 'data', that.columnBindList, 'select', false, false);
+                }
+            });
+        }
+
+        templateForBody() {
+            let page = $(nmHTML);
+            let that = this;
+
+            let dataSelector = new DataSelector({
+                pageThis: this, id: 'data', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data = data;
+                    that.state.dataType = type;
+                    $(that.wrapSelector('#data')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data = data;
+                    that.state.dataType = type;
+                    $(that.wrapSelector('#data')).trigger('change');
+                }
+            });
+            $(page).find('#data').replaceWith(dataSelector.toTagString());
+
+            return page;
+        }
+
+        render() {
+            super.render();
+            let that = this;
+
+            // render Subset
+            this.subsetEditor['data'] = new Subset({ 
+                pandasObject: '',
+                config: { name: 'Subset', category: this.name } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data'),
+                    pageThis: this,
+                    finish: function(code, state) {
+                        that.state.data = code;
+                        $(that.wrapSelector('#data')).val(code);
+                        that.state.dataType = state.returnType;
+                        $(that.wrapSelector('#data')).trigger('change');
+                    }
+                });
+
+            // bind column if data exist
+            if (this.state.data !== '') {
+                com_generator.vp_bindColumnSource(this, 'data', this.columnBindList, 'select', false, false);
+            }
+
+            // control display option
+            $(this.wrapSelector('.vp-st-option')).hide();
+            $(this.wrapSelector('.vp-st-option.' + this.state.testType)).show();
+        }
+
+        generateCode() {
+            let { 
+                testType, data, depVar, factor, factorA, factorB, covariate, sigLevel,
+                // Post hoc analysis option
+                tukeyHSD, tukey, scheffe, duncan, bonferroni,
+                // Display option
+                statistics, boxplot, equalVariance, interPlot 
+            } = this.state;
+
+            // get only text without '' or ""
+            let depVarText = $(this.wrapSelector('#depVar option:selected')).text();
+            let factorText = $(this.wrapSelector('#factor option:selected')).text();
+            let factorAText = $(this.wrapSelector('#factorA option:selected')).text();
+            let factorBText = $(this.wrapSelector('#factorB option:selected')).text();
+            let covariateText = $(this.wrapSelector('#covariate option:selected')).text();
+
+            let codeList = [];
+            let code = new com_String();
+
+            // test type label
+            let testTypeLabel = $(this.wrapSelector('#testType option:selected')).text();
+            code.appendFormatLine("# {0}", testTypeLabel);
+            code.appendFormat("vp_df = {0}.dropna().copy()", data);
+
+            switch (testType) {
+                case 'one-way':
+                    // 1. One-way ANOVA
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("_df = pd.DataFrame()");
+                    code.appendFormatLine("for k, v in  dict(list(vp_df.groupby({0})[{1}])).items():", factor, depVar);
+                    code.appendLine("    _df_t = v.reset_index(drop=True)");
+                    code.appendLine("    _df_t.name = k");
+                    code.append("    _df = pd.concat([_df, _df_t], axis=1)");
+
+                    // Display - Statistics
+                    if (statistics === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Statistics");
+                        code.appendLine("display(Markdown('### Statistics'))");
+                        code.appendLine("display(pd.DataFrame(data={'Count':_df.count(),'Mean':_df.mean(),'Std. Deviation':_df.std(),'Min':_df.min(),'Max':_df.max(),");
+                        code.appendLine("                           'Std. Error Mean':_df.apply(vp_sem),'Confidence interval':0.95,");
+                        code.append("                           'Lower':_df.apply(vp_confidence_interval).T[0],'Upper':_df.apply(vp_confidence_interval).T[1] }))");
+                    }
+                    // Display - Boxplot
+                    if (boxplot === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Boxplot");
+                        code.appendLine("import seaborn as sns");
+                        code.appendLine("import warnings");
+                        code.appendLine("with warnings.catch_warnings():");
+                        code.appendLine("    warnings.simplefilter(action='ignore', category=Warning)");
+                        code.appendLine("    sns.boxplot(data=_df)");
+                        code.append("    plt.show()");
+                    }
+                    // Display - Equal Variance
+                    if (equalVariance === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Equal Variance test (Levene)");
+                        code.appendLine("from scipy import stats");
+                        code.appendLine("_lst = []");
+                        code.appendLine("_df.apply(lambda x: _lst.append(x.dropna()))");
+                        code.appendLine("_res = stats.levene(*_lst, center='mean')");
+                        code.appendLine("display(Markdown('### Equal Variance test (Levene)'))");
+                        code.append("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue}, index=['Equal Variance test (Levene)']))");
+                    }
+
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# One-way ANOVA");
+                    code.appendLine("import statsmodels.formula.api as smf");
+                    code.appendLine("from statsmodels.stats.anova import anova_lm");
+                    code.appendFormatLine("_model  = smf.ols('{0} ~ C({1})', vp_df)", depVarText, factorText);
+                    code.appendLine("_result = _model.fit()");
+                    code.appendLine("_dfr = anova_lm(_result)");
+                    code.appendLine("_dfr.loc['Total','df'] = _dfr['df'].sum()");
+                    code.appendLine("_dfr.loc['Total','sum_sq'] = _dfr['sum_sq'].sum()");
+                    code.appendLine("display(Markdown('### One-way ANOVA'))");
+                    code.append("display(_dfr)");
+
+                    // Post hoc analysis - Tukey HSD
+                    if (tukeyHSD === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Post-hoc: Tukey HSD");
+                        code.appendLine("from statsmodels.sandbox.stats.multicomp import MultiComparison");
+                        code.appendFormatLine("_res = MultiComparison(vp_df[{0}], vp_df[{1}]).tukeyhsd(alpha={2})", depVar, factor, sigLevel);
+                        code.appendLine("display(Markdown('### Post-hoc: Tukey HSD'))");
+                        code.append("display(_res.summary())");
+                    }
+                    // Post hoc analysis - Bonferroni
+                    if (bonferroni === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Post-hoc: Bonferroni");
+                        code.appendLine("from statsmodels.sandbox.stats.multicomp import MultiComparison");
+                        code.appendFormatLine("_res = MultiComparison(vp_df[{0}], vp_df[{1}]).allpairtest(stats.ttest_ind,alpha={2},method='bonf')", depVar, factor, sigLevel);
+                        code.appendLine("display(Markdown('### Post-hoc: Bonferroni'))");
+                        code.append("display(_res[0])");
+                    }
+
+                    if (tukey === true || scheffe === true || duncan === true) {
+                        codeList.push("!pip install scikit-posthocs");
+
+                        // Post hoc analysis - Tukey
+                        if (tukey === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Post-hoc: Tukey");
+                            code.appendLine("import scikit_posthocs as sph");
+                            code.appendLine("display(Markdown('### Post-hoc: Tukey'))");
+                            code.appendFormat("display(sph.posthoc_tukey(vp_df, val_col={0}, group_col={1}))", depVar, factor);
+                        }
+                        // Post hoc analysis - Scheffe
+                        if (scheffe === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Post-hoc: Scheffe");
+                            code.appendLine("import scikit_posthocs as sph");
+                            code.appendLine("display(Markdown('### Post-hoc: Scheffe'))");
+                            code.appendFormat("display(sph.posthoc_scheffe(vp_df, val_col={0}, group_col={1}))", depVar, factor);
+                            
+                        }
+                        // Post hoc analysis - duncan
+                        if (duncan === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Post-hoc: Duncan");
+                            code.appendLine("import scikit_posthocs as sph");
+                            code.appendLine("display(Markdown('### Post-hoc: Duncan'))");
+                            code.appendFormat("display(sph.posthoc_dunn(vp_df, val_col={0}, group_col={1}))", depVar, factor);
+                        }
+                    }
+
+                    break;
+                case 'two-way':
+                    // 1. Two-way ANOVA
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("_df = pd.DataFrame()");
+                    code.appendFormatLine("for k, v in  dict(list(vp_df.groupby([{0},{1}])[{2}])).items():", factorB, factorA, depVar);
+                    code.appendLine("    _df_t = v.reset_index(drop=True)");
+                    code.appendLine("    _df_t.name = k");
+                    code.appendLine("    _df = pd.concat([_df, _df_t], axis=1)");
+                    code.append("    _df.columns = [[x[0] for x in _df.columns],[x[1] for x in _df.columns]]");
+
+                    // Display - Statistics
+                    if (statistics === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Statistics");
+                        code.appendLine("display(Markdown('### Statistics'))");
+                        code.appendLine("display(pd.DataFrame(data={'Count':_df.count(),'Mean':_df.mean(),'Std. Deviation':_df.std(),'Min':_df.min(),'Max':_df.max(),");
+                        code.appendLine("                   'Std. Error Mean':_df.apply(vp_sem),'Confidence interval':0.95,");
+                        code.append("                   'Lower':_df.apply(vp_confidence_interval).T[0],'Upper':_df.apply(vp_confidence_interval).T[1] }))");
+                    }
+                    // Display - Boxplot
+                    if (boxplot === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Boxplot");
+                        code.appendLine("import seaborn as sns");
+                        code.appendLine("import warnings");
+                        code.appendLine("with warnings.catch_warnings():");
+                        code.appendLine("    warnings.simplefilter(action='ignore', category=Warning)");
+                        code.appendLine("    sns.boxplot(data=_df)");
+                        code.append("    plt.show()");
+                    }
+                    // Display - Equal Variance test
+                    if (equalVariance === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Equal Variance test (Levene)");
+                        code.appendLine("from scipy import stats");
+                        code.appendLine("_lst = []");
+                        code.appendLine("_df.apply(lambda x: _lst.append(x.dropna()))");
+                        code.appendLine("_res = stats.levene(*_lst, center='mean')");
+                        code.appendLine("display(Markdown('### Equal Variance test (Levene)'))");
+                        code.append("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue}, index=['Equal Variance test (Levene)']))");
+                    }
+                    
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# Two-way ANOVA");
+                    code.appendLine("import statsmodels.formula.api as smf");
+                    code.appendLine("from statsmodels.stats.anova import anova_lm");
+                    code.appendFormatLine("_model  = smf.ols('{0} ~ C({1}) + C({2}) + C({3}):C({4})', vp_df)", depVarText, factorAText, factorBText, factorAText, factorBText);
+                    code.appendLine("_result = _model.fit()");
+                    code.appendLine("_dfr = anova_lm(_result)");
+                    code.appendLine("_dfr.loc['Total','df'] = _dfr['df'].sum()");
+                    code.appendLine("_dfr.loc['Total','sum_sq'] = _dfr['sum_sq'].sum()");
+                    code.appendLine("display(Markdown('### Two-way ANOVA'))");
+                    code.append("display(_dfr)");
+
+                    // Display - Interaction plot
+                    if (interPlot === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Interaction plot");
+                        code.appendLine("from statsmodels.graphics.factorplots import interaction_plot");
+                        code.appendLine("import warnings");
+                        code.appendLine("with warnings.catch_warnings():");
+                        code.appendLine("    warnings.simplefilter(action='ignore', category=Warning)");
+                        code.appendFormatLine("    fig = interaction_plot(x=vp_df[{0}], trace=vp_df[{1}], response=vp_df[{2}])", factorA, factorB, depVar);
+                        code.append("    plt.show()");
+                    }
+                    // Post hoc analysis - Tukey HSD
+                    if (tukeyHSD === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Post-hoc: Tukey HSD");
+                        code.appendLine("from statsmodels.sandbox.stats.multicomp import MultiComparison");
+                        code.appendFormatLine("_res = MultiComparison(vp_df[{0}], vp_df[{1}]).tukeyhsd(alpha={2})", depVar, factorA, sigLevel);
+                        code.appendLine("display(Markdown('### Post-hoc: Tukey HSD'))");
+                        code.append("display(_res.summary())");
+                    }
+                    // Post hoc analysis - Bonferroni
+                    if (bonferroni === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Post-hoc: Bonferroni");
+                        code.appendLine("from statsmodels.sandbox.stats.multicomp import MultiComparison");
+                        code.appendFormatLine("_res = MultiComparison(vp_df[{0}], vp_df[{1}]).allpairtest(stats.ttest_ind,alpha={2},method='bonf')", depVar, factorA, sigLevel);
+                        code.appendLine("display(Markdown('### Post-hoc: Bonferroni'))");
+                        code.append("display(_res[0])");
+                    }
+                    if (tukey === true || scheffe === true || duncan === true) {
+                        // Add installation code
+                        codeList.push("!pip install scikit-posthocs");
+
+                        // Post hoc analysis - Tukey
+                        if (tukey === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Post-hoc: Tukey");
+                            code.appendLine("import scikit_posthocs as sph");
+                            code.appendLine("display(Markdown('### Post-hoc: Tukey'))");
+                            code.appendFormat("display(sph.posthoc_tukey(vp_df, val_col={0}, group_col={1}))", depVar, factorA);
+                        }
+                        // Post hoc analysis - Scheffe
+                        if (scheffe === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Post-hoc: Scheffe");
+                            code.appendLine("import scikit_posthocs as sph");
+                            code.appendLine("display(Markdown('### Post-hoc: Scheffe'))");
+                            code.appendFormat("display(sph.posthoc_scheffe(vp_df, val_col={0}, group_col={1}))", depVar, factorA);
+                        }
+                        // Post hoc analysis - Duncan
+                        if (duncan === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Post-hoc: Duncan");
+                            code.appendLine("import scikit_posthocs as sph");
+                            code.appendLine("display(Markdown('### Post-hoc: Duncan'))");
+                            code.appendFormat("display(sph.posthoc_dunn(vp_df, val_col={0}, group_col={1}))", depVar, factorA);
+                        }
+                    }
+                    break;
+                case 'ancova':
+                    // 1. ANCOVA
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("_df = pd.DataFrame()");
+                    code.appendFormatLine("for k, v in  dict(list(vp_df.groupby({0})[{1}])).items():", factor, depVar);
+                    code.appendLine("    _df_t = v.reset_index(drop=True)");
+                    code.appendLine("    _df_t.name = k");
+                    code.append("    _df = pd.concat([_df, _df_t], axis=1)");
+
+                    // Display - Statistics
+                    if (statistics === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Statistics");
+                        code.appendLine("display(Markdown('### Statistics'))");
+                        code.appendLine("display(pd.DataFrame(data={'Count':_df.count(),'Mean':_df.mean(),'Std. Deviation':_df.std(),'Min':_df.min(),'Max':_df.max(),");
+                        code.appendLine("                   'Std. Error Mean':_df.apply(vp_sem),'Confidence interval':0.95,");
+                        code.append("                   'Lower':_df.apply(vp_confidence_interval).T[0],'Upper':_df.apply(vp_confidence_interval).T[1] }))");
+                    }
+                    // Display - Boxplot
+                    if (boxplot === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Boxplot");
+                        code.appendLine("import seaborn as sns");
+                        code.appendLine("import warnings");
+                        code.appendLine("with warnings.catch_warnings():");
+                        code.appendLine("    warnings.simplefilter(action='ignore', category=Warning)");
+                        code.appendLine("    sns.boxplot(data=_df)");
+                        code.append("    plt.show()");
+                    }
+
+                    // Add installation code : # pip install pingouin
+                    codeList.push("!pip install pingouin");
+
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# ANCOVA - Analysis of covariance");
+                    code.appendLine("import pingouin as pg");
+                    code.appendLine("display(Markdown('### ANCOVA - Analysis of covariance'))");
+                    code.appendFormat("display(pg.ancova(data=vp_df, dv={0}, between={1}, covar={2}))", depVar, factor, covariate);
+                    break;
+            }
+            
+            codeList.push(code.toString());
+
+            return codeList;
+        }
+
+    }
+
+    return Anova;
+});
\ No newline at end of file
diff --git a/visualpython/js/m_stats/CorrAnalysis.js b/visualpython/js/m_stats/CorrAnalysis.js
new file mode 100644
index 00000000..1620b82f
--- /dev/null
+++ b/visualpython/js/m_stats/CorrAnalysis.js
@@ -0,0 +1,201 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : CorrAnalysis.js
+ *    Author          : Black Logic
+ *    Note            : Correlation Analysis
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 05. 24
+ *    Change Date     :
+ */
+
+//============================================================================
+// [CLASS] CorrAnalysis
+//============================================================================
+define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/corrAnalysis.html'),
+    'vp_base/js/com/com_util',
+    'vp_base/js/com/com_Const',
+    'vp_base/js/com/com_String',
+    'vp_base/js/com/component/PopupComponent',
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/com/component/MultiSelector',
+    'vp_base/js/m_apps/Subset'
+], function(eqHTML, com_util, com_Const, com_String, PopupComponent, DataSelector, MultiSelector, Subset) {
+
+    /**
+     * CorrAnalysis
+     */
+    class CorrAnalysis extends PopupComponent {
+        _init() {
+            super._init();
+            /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd'];
+
+            this.state = {
+                data: '',
+                variable: [],
+                corrType: 'pearson',
+                corrAnlaysis: true,
+                corrMatrix: true,
+                corrHeatmap: false,
+                scatterMatrix: false,
+                ...this.state
+            };
+
+            this.subsetEditor = null;
+            this.columnSelector = null;
+        }
+
+        _bindEvent() {
+            super._bindEvent();
+            /** Implement binding events */
+            var that = this;
+
+            $(this.wrapSelector('#data')).on('change', function() {
+                let data = $(this).val();
+                that.handleVariableChange(data);
+            });
+        }
+
+        handleVariableChange(data) {
+            this.state.data = data;
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#variable'),
+                { mode: 'columns', parent: data, showDescription: false }
+        );
+        }
+
+        templateForBody() {
+            let page = $(eqHTML);
+            let that = this;
+
+            // generate dataselector
+            let dataSelector = new DataSelector({
+                pageThis: this, id: 'data', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                }
+            });
+            $(page).find('#data').replaceWith(dataSelector.toTagString());
+
+            return page;
+        }
+
+        render() {
+            super.render();
+            let that = this;
+
+            // render Subset
+            this.subsetEditor = new Subset({ 
+                pandasObject: '',
+                config: { name: 'Subset', category: this.name } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data'),
+                    pageThis: this,
+                    finish: function(code) {
+                        $(that.wrapSelector('#data')).val(code);
+                        that.handleVariableChange(code);
+                    }
+                });
+
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#variable'),
+                        { mode: 'columns', parent: this.state.data, selectedList: this.state.variable?.map(x=>x.code), showDescription: false }
+                    );
+        }
+
+        generateCode() {
+            let { data, variable, corrType, corrAnlaysis, corrMatrix, corrHeatmap, scatterMatrix } = this.state;
+            let codeList = [];
+            let code = new com_String();
+
+            // data declaration
+            code.appendFormat("vp_df = {0}", data);
+            if (this.columnSelector) {
+                let columns = this.columnSelector.getDataList();
+                this.state.variable = columns;
+                if (columns.length > 0) {
+                    code.appendFormat("[[{0}]]", columns.map(x => x.code).join(', '));
+                }
+            }
+            code.append('.dropna().copy()');
+
+            let corrTypeLabel = $(this.wrapSelector('#corrType option:selected')).text();
+
+            // Display option : Correlation Analysis
+            if (corrAnlaysis === true) {
+                // Inner function : vp_confidence_interval_corr
+                this.addCheckModules('vp_confidence_interval_corr');
+                
+                code.appendLine();
+                code.appendLine();
+                code.appendLine("# Correlation Analysis");
+                code.appendLine("from scipy import stats");
+                code.appendLine("from IPython.display import display, Markdown");
+                code.appendLine("_dfr = pd.DataFrame()");
+                code.appendLine("for i, col1 in enumerate(vp_df.columns):");
+                code.appendLine("    for j, col2 in enumerate(vp_df.columns):");
+                code.appendLine("        if i >= j: continue");
+                code.appendLine("        if pd.api.types.is_numeric_dtype(vp_df[col1]) and pd.api.types.is_numeric_dtype(vp_df[col2]):");
+                code.appendFormatLine("            _res = vp_confidence_interval_corr(vp_df[col1], vp_df[col2], method='{0}')", corrType);
+                code.appendLine("            _df_t = pd.DataFrame(data={'Variable1':col1,'Variable2':col2,'N':vp_df[col1].size,'Correlation coefficient':_res[0],");
+                code.appendLine("                                   'p-value':_res[1],'Lower(95%)':_res[2],'Upper(95%)':_res[3]}, index=[0])");
+                code.appendLine("            _dfr = pd.concat([_dfr, _df_t]).reset_index(drop=True)");
+                code.appendFormatLine("display(Markdown('### Correlation Analysis: {0}'))", corrTypeLabel.replace("'", "\\'"));
+                code.append("display(_dfr)");
+            }
+
+            // Display option : Correlation Matrix
+            if (corrMatrix === true) {
+                code.appendLine();
+                code.appendLine();
+                code.appendFormatLine("# Correlation matrix: {0}", corrTypeLabel);
+                code.appendLine("from IPython.display import display");
+                code.appendFormat("display(vp_df.corr(method='{0}', numeric_only=True).round(2))", corrType);
+            }
+
+            if (corrHeatmap === true || scatterMatrix === true) {
+                code.appendLine();
+                code.appendLine();
+                code.appendLine("# Chart");
+                code.appendLine("import seaborn as sns");
+                code.appendLine("import warnings");
+                code.appendLine("with warnings.catch_warnings():");
+                code.append("    warnings.simplefilter(action='ignore', category=Warning)");
+                // Display option : Correlation Heatmap
+                if (corrHeatmap === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("    # Heatmap");
+                    code.appendFormatLine("    sns.heatmap(vp_df.corr(method='{0}', numeric_only=True), annot=True, fmt='.2f', cmap='coolwarm')", corrType);
+                    code.appendFormatLine("    plt.title('Correlation heatmap: {0}')", corrTypeLabel.replace("'", "\\'"));
+                    code.append("    plt.show()");
+                }
+                // Display option : Scatter Matrix
+                if (scatterMatrix === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("    # Scatter matrix");
+                    code.appendLine("    pd.plotting.scatter_matrix(vp_df)");
+                    code.append("    plt.show()");
+                }
+            }
+            codeList.push(code.toString());
+
+            return codeList;
+        }
+
+    }
+
+    return CorrAnalysis;
+});
\ No newline at end of file
diff --git a/visualpython/js/m_stats/DescStats.js b/visualpython/js/m_stats/DescStats.js
new file mode 100644
index 00000000..e6a9aa45
--- /dev/null
+++ b/visualpython/js/m_stats/DescStats.js
@@ -0,0 +1,320 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : DescStats.js
+ *    Author          : Black Logic
+ *    Note            : Descriptive Statistics
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 05. 31
+ *    Change Date     :
+ */
+
+//============================================================================
+// [CLASS] DescStats
+//============================================================================
+define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/descStats.html'),
+    __VP_CSS_LOADER__('vp_base/css/m_stats/descStats'),
+    'vp_base/js/com/com_util',
+    'vp_base/js/com/com_Const',
+    'vp_base/js/com/com_String',
+    'vp_base/js/com/component/PopupComponent',
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/com/component/MultiSelector',
+    'vp_base/js/m_apps/Subset'
+], function(eqHTML, dsCss, com_util, com_Const, com_String, PopupComponent, DataSelector, MultiSelector, Subset) {
+
+    /**
+     * DescStats
+     */
+    class DescStats extends PopupComponent {
+        _init() {
+            super._init();
+            /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd'];
+
+            this.state = {
+                data: '',
+                variable: [],
+                // Central tendency
+                mean: true,
+                median: false,
+                mode: false,
+                sum: true,
+                // Dispersion
+                min: false,
+                max: false,
+                range: false,
+                std: true,
+                var: true,
+                semean: false,
+                skew: false,
+                kurtosis: false,
+                // Percentile values
+                quantile: true,
+                usePercentile: false,
+                percentiles: [],
+                // Frequency table
+                frequency: true,
+                percent: true,
+                validPercent: true,
+                cumulativePercent: true,
+                noUniqVals: 10,
+                // Display
+                histogram: true,
+                scatterMatrix: true,
+                boxplot: true,
+                ...this.state
+            };
+
+            this.subsetEditor = null;
+            this.columnSelector = null;
+        }
+
+        _bindEvent() {
+            super._bindEvent();
+            /** Implement binding events */
+            var that = this;
+
+            // data selection
+            $(this.wrapSelector('#data')).on('change', function() {
+                let data = $(this).val();
+                that.handleVariableChange(data);
+            });
+
+            // use percentile
+            $(this.wrapSelector('#usePercentile')).on('change', function() {
+                let checked = $(this).prop('checked');
+                if (checked === true) {
+                    // enable percentile editing
+                    $(that.wrapSelector('#percentile')).prop('disabled', false);
+                    $(that.wrapSelector('#addPercentile')).prop('disabled', false);
+                    $(that.wrapSelector('.vp-percentile-box')).removeClass('disabled');
+                } else {
+                    // disable percentile editing
+                    $(that.wrapSelector('#percentile')).prop('disabled', true);
+                    $(that.wrapSelector('#addPercentile')).prop('disabled', true);
+                    $(that.wrapSelector('.vp-percentile-box')).addClass('disabled');
+
+                }
+            });
+
+            // add percentile
+            $(this.wrapSelector('#addPercentile')).on('click', function() {
+                let newVal = $(that.wrapSelector('#percentile')).val();
+                if (newVal && newVal !== '') {
+                    let newValNum = parseInt(newVal);
+                    that.addPercentile(newValNum);
+                    that.state.percentiles.push(newValNum);
+                    $(that.wrapSelector('#percentile')).val('');
+                }
+            });
+        }
+
+        handleVariableChange(data) {
+            this.state.data = data;
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#variable'),
+                { mode: 'columns', parent: data, showDescription: false }
+            );
+        }
+
+        addPercentile(percentile) {
+            if (this.state.percentiles.indexOf(percentile) === -1) {
+                $(this.wrapSelector('.vp-percentile-box')).append(
+                    $(`<div class="vp-percentile-item">
+                        <div class="vp-percentile-value">${percentile}</div>
+                        <div class="vp-percentile-remove vp-icon-close-small"></div>
+                    </div>`));
+    
+                // delete percentile
+                let that = this;
+                $(this.wrapSelector('.vp-percentile-box:not(.disabled) .vp-percentile-remove')).on('click', function() {
+                    if (that.state.usePercentile === true) {
+                        let delVal = parseInt($(this).parent().find('.vp-percentile-value').text());
+                        that.state.percentiles = that.state.percentiles.filter(x => x !== delVal);
+                        $(this).closest('.vp-percentile-item').remove();
+                    }
+                });
+            }
+        }
+
+        templateForBody() {
+            let page = $(eqHTML);
+            let that = this;
+
+            // generate dataselector
+            let dataSelector = new DataSelector({
+                pageThis: this, id: 'data', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                }
+            });
+            $(page).find('#data').replaceWith(dataSelector.toTagString());
+
+            return page;
+        }
+
+        render() {
+            super.render();
+            let that = this;
+
+            // render Subset
+            this.subsetEditor = new Subset({ 
+                pandasObject: '',
+                config: { name: 'Subset', category: this.name } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data'),
+                    pageThis: this,
+                    finish: function(code) {
+                        $(that.wrapSelector('#data')).val(code);
+                        that.handleVariableChange(code);
+                    }
+                });
+
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#variable'),
+                        { mode: 'columns', parent: this.state.data, showDescription: false }
+                    );
+        }
+
+        generateCode() {
+            let { data, variable,
+                // Central tendency
+                mean,median,mode,sum,
+                // Dispersion
+                min,max,range,std,variance,semean,skew,kurtosis,
+                // Percentile values
+                quantile,usePercentile,percentiles,
+                // Frequency table
+                frequency,percent,validPercent,cumulativePercent,noUniqVals,
+                // Display
+                histogram,scatterMatrix,boxplot
+            } = this.state;
+            let codeList = [];
+            let code = new com_String();
+
+            // data declaration
+            code.appendFormat("vp_df = {0}", data);
+            if (this.columnSelector) {
+                let columns = this.columnSelector.getDataList();
+                if (columns.length > 0) {
+                    code.appendFormat("[[{0}]]", columns.map(x => x.code).join(', '));
+                }
+            }
+            code.appendLine('.copy()');
+
+            // Descriptive statistics
+            code.appendLine();
+            code.appendLine("# Descriptive statistics");
+            code.appendLine("from IPython.display import display, Markdown");
+            code.appendLine("display(Markdown('### Descriptive statistics'))");
+            code.appendLine("display(pd.DataFrame({");
+            code.appendLine("    'N Total':vp_df.shape[0],");
+            code.appendLine("    'N Valid':vp_df.count(numeric_only=True),");
+            code.appendLine("    'N Missing':vp_df.loc[:,vp_df.apply(pd.api.types.is_numeric_dtype)].isnull().sum(),");
+            if (mean === true)      code.appendLine("    'Mean':vp_df.mean(numeric_only=True),");
+            if (median === true)    code.appendLine("    'Median':vp_df.median(numeric_only=True),");
+            if (mode === true)      code.appendLine("    'Mode':vp_df.mode(numeric_only=True).iloc[0],");
+            if (sum === true)       code.appendLine("    'Sum':vp_df.sum(numeric_only=True),");
+            if (min === true)       code.appendLine("    'Minimun':vp_df.min(numeric_only=True),");
+            if (max === true)       code.appendLine("    'Maximum':vp_df.max(numeric_only=True),");
+            if (range === true)     code.appendLine("    'Range':vp_df.max(numeric_only=True) - vp_df.min(numeric_only=True),");
+            if (std === true)       code.appendLine("    'Std. deviation':vp_df.std(numeric_only=True),");
+            if (variance === true)  code.appendLine("    'Variance':vp_df.var(numeric_only=True),");
+            if (semean === true)    code.appendLine("    'S.E. mean':vp_df.std(numeric_only=True)/np.sqrt(vp_df.count(numeric_only=True)),");
+            if (skew === true)      code.appendLine("    'Skewness':vp_df.skew(numeric_only=True),");
+            if (kurtosis === true)  code.appendLine("    'Kurtosis':vp_df.kurtosis(numeric_only=True),");
+            let sortedPercentiles = [];
+            if (quantile === true) {
+                sortedPercentiles = [25, 50, 75];
+            }
+            if (usePercentile === true && percentiles.length > 0) {
+                sortedPercentiles = [...sortedPercentiles, ...percentiles];
+            }
+            sortedPercentiles.sort((a, b) => { return a - b; });
+            sortedPercentiles.forEach(num => {
+                code.appendFormatLine("    'Percentile: {0}':vp_df.quantile(q={1}, numeric_only=True),", num, (num / 100).toFixed(2));
+            });
+            code.appendLine("}).round(3).T)");
+
+            // Frequency table
+            code.appendLine();
+            code.appendLine("# Frequency table");
+            code.appendLine("display(Markdown('### Frequency table'))");
+            code.appendLine("for col in vp_df.columns:");
+            code.appendFormatLine("    if pd.api.types.is_numeric_dtype(vp_df[col]) and  vp_df[col].value_counts().size > {0}:", noUniqVals);
+            code.appendFormatLine("        _bins = {0}", noUniqVals);
+            code.appendLine("    else: _bins = None");
+            code.appendLine("        ");
+            code.appendLine("    _dfr = pd.DataFrame({");
+            if (frequency === true) code.appendLine("              'Frequency':vp_df[col].value_counts(bins=_bins, sort=False),");
+            if (percent === true) code.appendLine("              'Percent':100*(vp_df[col].value_counts(bins=_bins, sort=False) / vp_df[col].size),");
+            if (validPercent === true) code.appendLine("              'Valid percent':100*(vp_df[col].value_counts(bins=_bins, sort=False)/vp_df[col].count())");
+            code.appendLine("}).round(2)");
+            if (cumulativePercent === true) code.appendLine("    _dfr['Cumulative percent'] = _dfr['Valid percent'].cumsum()");
+            code.appendLine("    _dfr.loc['N Valid',:] =  _dfr.iloc[:,:3].sum()");
+            code.appendLine("    _dfr.loc['N Missing','Frequency'] =  vp_df[col].isnull().sum()");
+            code.appendLine("    _dfr.loc['N Total','Frequency'] =  vp_df[col].size");
+            code.append("    display(_dfr)");
+
+            // Display option
+            if (histogram || scatterMatrix || boxplot) {
+                code.appendLine();
+                code.appendLine();
+                code.appendLine("# Charts");
+                code.appendLine("import seaborn as sns");
+                code.appendLine("import warnings");
+                code.appendLine("with warnings.catch_warnings():");
+                code.append("    warnings.simplefilter(action='ignore', category=Warning)");
+                if (histogram === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("    # Histogram");
+                    code.appendLine("    idx = 1");
+                    code.appendLine("    for col in vp_df.columns:");
+                    code.appendLine("        plt.subplot(2,2, idx)");
+                    code.appendFormatLine("        if pd.api.types.is_numeric_dtype(vp_df[col]) and  vp_df[col].value_counts().size > {0}:", noUniqVals);
+                    code.appendLine("            sns.histplot(data=vp_df, x=col, kde=True)");
+                    code.appendLine("        else:");
+                    code.appendLine("            sns.countplot(data=vp_df, x=col)");
+                    code.appendLine("        ");
+                    code.appendLine("        if idx < 4:");
+                    code.appendLine("            idx += 1");
+                    code.appendLine("        else:");
+                    code.appendLine("            idx = 1");
+                    code.appendLine("            plt.tight_layout()");
+                    code.append("            plt.show()");
+                }
+                if (scatterMatrix === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("    # Scatter matrix        ");
+                    code.appendLine("    pd.plotting.scatter_matrix(vp_df, marker='o', hist_kwds={'bins': 30}, s=30, alpha=.8)");
+                    code.append("    plt.show()");
+                }
+                if (boxplot === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("    # Boxplot");
+                    code.appendLine("    sns.boxplot(vp_df)");
+                    code.append("    plt.show()");
+                }
+            }
+
+            return code.toString();
+        }
+
+    }
+
+    return DescStats;
+});
\ No newline at end of file
diff --git a/visualpython/js/m_stats/EqualVarTest.js b/visualpython/js/m_stats/EqualVarTest.js
index 3fe50bb9..9a01cda9 100644
--- a/visualpython/js/m_stats/EqualVarTest.js
+++ b/visualpython/js/m_stats/EqualVarTest.js
@@ -3,7 +3,7 @@
  *    Description     : GUI-based Python code generator
  *    File Name       : EqualVarTest.js
  *    Author          : Black Logic
- *    Note            : Equal Variance Test
+ *    Note            : Equal Variance test
  *    License         : GNU GPLv3 with Visual Python special exception
  *    Date            : 2023. 05. 09
  *    Change Date     :
@@ -13,12 +13,16 @@
 // [CLASS] EqualVarTest
 //============================================================================
 define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/equalVarTest.html'),
     'vp_base/js/com/com_util',
     'vp_base/js/com/com_Const',
     'vp_base/js/com/com_String',
+    'vp_base/js/com/com_generatorV2',
     'vp_base/js/com/component/PopupComponent',
-    'vp_base/js/com/component/DataSelector'
-], function(com_util, com_Const, com_String, PopupComponent, DataSelector) {
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/com/component/MultiSelector',
+    'vp_base/js/m_apps/Subset'
+], function(eqHTML, com_util, com_Const, com_String, com_generator, PopupComponent, DataSelector, MultiSelector, Subset) {
 
     /**
      * EqualVarTest
@@ -27,36 +31,212 @@ define([
         _init() {
             super._init();
             /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd'];
+
+            this.state = {
+                testType: 'bartlett',
+                inputType: 'long-data',
+                data: '',
+                variableMulti: [],
+                variable: '',
+                factor: '',
+                center: 'mean',
+                histogram: true,
+                ...this.state
+            };
+
+            this.subsetEditor = {};
+            this.columnSelector = {};
         }
 
         _bindEvent() {
             super._bindEvent();
             /** Implement binding events */
             var that = this;
+
+            // change test type
+            $(this.wrapSelector('#testType')).on('change', function() {
+                let testType = $(this).val();
+                that.state.testType = testType;
+
+                $(that.wrapSelector('.vp-st-option')).hide();
+                $(that.wrapSelector('.vp-st-option.' + testType)).show();
+            });
+
+            // change input type
+            $(this.wrapSelector('input[name="inputType"]:radio')).on('change', function() {
+                let inputType = $(this).val();
+                that.state.inputType = inputType;
+                $(that.wrapSelector('.vp-variable-box')).hide();
+                $(that.wrapSelector('.vp-variable-box.' + inputType)).show();
+            });
+
+            // data change event
+            $(this.wrapSelector('#data')).on('change', function() {
+                let data = $(this).val();
+                that.handleVariableChange(data);
+            });
         }
 
-        templateForBody() {
-            /** Implement generating template */
-            return `This is sample.
-            <input type="text" id="sample" class="vp-state vp-input" readonly="" />`;
+        handleVariableChange(data) {
+            this.state.data = data;
+            // render column selector
+            com_generator.vp_bindColumnSource(this, 'data', ['variable', 'factor'], 'select', false, false);
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#variableMulti'),
+                { mode: 'columns', parent: data, showDescription: false }
+            );
         }
 
-        render() {
-            super.render();
+        templateForBody() {
+            let page = $(eqHTML);
+            let that = this;
 
+            // generate dataselector
             let dataSelector = new DataSelector({
-                type: 'data',
-                pageThis: this,
-                id: 'sample',
-                finish: function() {
-                    ;
+                pageThis: this, id: 'data', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
                 }
             });
-            $(this.wrapSelector('#sample')).replaceWith(dataSelector.toTagString());
+            $(page).find('#data').replaceWith(dataSelector.toTagString());
+
+            return page;
+        }
+
+        render() {
+            super.render();
+            let that = this;
+
+             // render Subset
+             this.subsetEditor = new Subset({ 
+                pandasObject: '',
+                config: { name: 'Subset', category: this.name } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data'),
+                    pageThis: this,
+                    finish: function(code) {
+                        $(that.wrapSelector('#data')).val(code);
+                        that.handleVariableChange(code);
+                    }
+                });
+
+            if (this.state.data !== '') {
+                // render column selector
+                com_generator.vp_bindColumnSource(this, 'data', ['variable', 'factor'], 'select', false, false);
+            }
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#variableMulti'),
+                        { mode: 'columns', parent: this.state.data, selectedList: this.state.variableMulti?.map(x => x.code), showDescription: false }
+                    );
+
+            $(this.wrapSelector('.vp-variable-box')).hide();
+            $(this.wrapSelector('.vp-variable-box.' + this.state.inputType)).show();
+
+            // control display option
+            $(this.wrapSelector('.vp-st-option')).hide();
+            $(this.wrapSelector('.vp-st-option.' + this.state.testType)).show();
         }
 
         generateCode() {
-            return "print('sample code')";
+            let { testType, inputType, data, variable, factor, center, histogram } = this.state;
+            let codeList = [];
+            let code = new com_String();
+            let that = this;
+
+            // test type label
+            let testTypeLabel = $(this.wrapSelector('#testType option:selected')).text();
+            code.appendFormatLine("# {0}", testTypeLabel);
+
+            if (inputType === 'long-data') {
+                code.appendFormatLine("vp_df = {0}.dropna().copy()", data);
+                code.appendLine("_df = pd.DataFrame()");
+                code.appendFormatLine("for k, v in  dict(list(vp_df.groupby({0})[{1}])).items():", factor, variable);
+                code.appendLine("    _df_t = v.reset_index(drop=True)");
+                code.appendLine("    _df_t.name = k");
+                code.append("    _df = pd.concat([_df, _df_t], axis=1)");
+            } else if (inputType === 'wide-data') {
+                // get variable multi
+                let columns = this.columnSelector.getDataList();
+                this.state.variableMulti = columns;
+                code.appendFormatLine("vp_df = {0}[[{1}]].copy()", data, columns.map(x => x.code).join(', ')); // without dropna
+                code.append("_df = vp_df.copy()");
+            }
+
+            // add variance code
+            code.appendLine();
+            code.appendLine();
+            code.appendLine("# Variance");
+            code.appendLine("from IPython.display import display, Markdown");
+            code.appendLine("from scipy import stats");
+            code.appendLine("_dfr = _df.var().to_frame()");
+            code.appendLine("_dfr.columns = ['Variance']");
+            code.append("display(_dfr)");
+
+            switch (testType) {
+                case 'bartlett':
+                    // 1. Bartlett test
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# Bartlett test");
+                    code.appendLine("_lst = []");
+                    code.appendLine("_df.apply(lambda x: _lst.append(x.dropna()))");
+                    code.appendLine("_res = stats.bartlett(*_lst)");
+                    code.appendLine("display(Markdown('### Bartlett test'))");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue},");
+                    code.append("                     index=['Equal Variance test (Bartlett)']))");
+                    break;
+                case 'levene':
+                    // 1. Levene test
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# Levene test");
+                    code.appendLine("_lst = []");
+                    code.appendLine("_df.apply(lambda x: _lst.append(x.dropna()))");
+                    code.appendFormatLine("_res = stats.levene(*_lst, center='{0}')", center);
+                    code.appendLine("display(Markdown('### Levene test'))");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue},");
+                    code.append("                     index=['Equal Variance test (Levene)']))");
+                    break;
+                case 'fligner':
+                    // 1. Fligner test
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# Fligner test");
+                    code.appendLine("_lst = []");
+                    code.appendLine("_df.apply(lambda x: _lst.append(x.dropna()))");
+                    code.appendFormatLine("_res = stats.fligner(*_lst, center='{0}')", center);
+                    code.appendLine("display(Markdown('### Fligner test'))");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue},");
+                    code.append("                     index=['Equal Variance test (Fligner)']))");
+                    break;
+            }
+
+            // Display option
+            if (histogram === true) {
+                code.appendLine();
+                code.appendLine();
+                code.appendLine("# Histogram");
+                code.appendLine("import seaborn as sns");
+                code.appendLine("import warnings");
+                code.appendLine("with warnings.catch_warnings():");
+                code.appendLine("    warnings.simplefilter(action='ignore', category=Warning)");
+                code.appendLine("    sns.histplot(_df, stat='density', kde=True)");
+                code.appendLine("    plt.title('Histogram')");
+                code.append("    plt.show()");
+            }
+            codeList.push(code.toString());
+
+            return codeList;
         }
 
     }
diff --git a/visualpython/js/m_stats/FactorAnalysis.js b/visualpython/js/m_stats/FactorAnalysis.js
new file mode 100644
index 00000000..c9bcb1e0
--- /dev/null
+++ b/visualpython/js/m_stats/FactorAnalysis.js
@@ -0,0 +1,295 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : FactorAnalysis.js
+ *    Author          : Black Logic
+ *    Note            : Factor Analysis
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 05. 24
+ *    Change Date     :
+ */
+
+//============================================================================
+// [CLASS] FactorAnalysis
+//============================================================================
+define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/factorAnalysis.html'),
+    'vp_base/js/com/com_util',
+    'vp_base/js/com/com_Const',
+    'vp_base/js/com/com_String',
+    'vp_base/js/com/component/PopupComponent',
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/com/component/MultiSelector',
+    'vp_base/js/m_apps/Subset'
+], function(eqHTML, com_util, com_Const, com_String, PopupComponent, DataSelector, MultiSelector, Subset) {
+
+    /**
+     * FactorAnalysis
+     */
+    class FactorAnalysis extends PopupComponent {
+        _init() {
+            super._init();
+            /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd'];
+            this.config.installButton = true;
+
+            this.state = {
+                data: '',
+                variable: [],
+                rotation: "'varimax'",
+                method: 'principal',
+                impute: 'drop',
+                extract: 'eigenvalue',
+                eigenvalue: 1,
+                factor: '',
+                corrMatrix: true,
+                screePlot: true,
+                ...this.state
+            };
+
+            this.rotationList = [
+                { label: "None", value: "None" },
+                { label: "varimax", value: "'varimax'" },
+                { label: "promax", value: "'promax'" },
+                { label: "oblimin", value: "'oblimin'" },
+                { label: "oblimax", value: "'oblimax'" },
+                { label: "quartimin", value: "'quartimin'" },
+                { label: "quartimax", value: "'quartimax'" },
+                { label: "equamax", value: "'equamax'" },
+            ];
+            this.methodList = [
+                { label: "minres", value: "minres" },
+                { label: "ml", value: "ml" },
+                { label: "principal", value: "principal" },
+            ];
+            this.imputeList = [
+                { label: "drop", value: "drop" },
+                { label: "mean", value: "mean" },
+                { label: "median", value: "median" },
+            ]
+
+            this.subsetEditor = null;
+            this.columnSelector = null;
+        }
+
+        _bindEvent() {
+            super._bindEvent();
+            /** Implement binding events */
+            var that = this;
+
+            $(this.wrapSelector('#data')).on('change', function() {
+                let data = $(this).val();
+                that.handleVariableChange(data);
+            });
+        }
+
+        handleVariableChange(data) {
+            this.state.data = data;
+            this.state.variable = [];
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#variable'),
+                { mode: 'columns', parent: data, showDescription: false }
+        );
+        }
+
+        templateForBody() {
+            let page = $(eqHTML);
+            let that = this;
+
+            // generate dataselector
+            let dataSelector = new DataSelector({
+                pageThis: this, id: 'data', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                }
+            });
+            $(page).find('#data').replaceWith(dataSelector.toTagString());
+
+            // generate rotation options
+            this.rotationList.forEach(obj => {
+                let selected = obj.value === that.state.rotation;
+                $(page).find('#rotation').append(`<option value="${obj.value}" ${selected?'selected':''}>${obj.label}</option>`);
+            });
+
+            // generate method options
+            this.methodList.forEach(obj => {
+                let selected = obj.value === that.state.method;
+                $(page).find('#method').append(`<option value="${obj.value}" ${selected?'selected':''}>${obj.label}</option>`);
+            });
+
+            // generate impute options
+            this.imputeList.forEach(obj => {
+                let selected = obj.value === that.state.impute;
+                $(page).find('#impute').append(`<option value="${obj.value}" ${selected?'selected':''}>${obj.label}</option>`);
+            });
+
+            return page;
+        }
+
+        render() {
+            super.render();
+            let that = this;
+
+            // render Subset
+            this.subsetEditor = new Subset({ 
+                pandasObject: '',
+                config: { name: 'Subset', category: this.name } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data'),
+                    pageThis: this,
+                    finish: function(code) {
+                        $(that.wrapSelector('#data')).val(code);
+                        that.handleVariableChange(code);
+                    }
+                });
+
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#variable'),
+                        { mode: 'columns', parent: this.state.data, selectedList: this.state.variable.map(x=>x.code), showDescription: false }
+                    );
+        }
+
+        generateInstallCode() {
+            return [ '!pip install factor-analyzer'];
+        }
+
+        generateCode() {
+            let { data, variable, rotation, method, impute, extract, eigenvalue, factor, corrMatrix, screePlot } = this.state;
+            let codeList = [];
+            let code = new com_String();
+
+            // data declaration
+            code.appendFormat("vp_df = {0}", data);
+            if (this.columnSelector) {
+                let columns = this.columnSelector.getDataList();
+                this.state.variable = columns;
+                if (columns.length > 0) {
+                    code.appendFormat("[[{0}]]", columns.map(x => x.code).join(', '));
+                }
+            }
+            code.appendLine('.dropna().copy()');
+
+            // KMO(Kaiser-Meyer-Olkin) measure of sampling adequacy
+            code.appendLine();
+            code.appendLine("# KMO(Kaiser-Meyer-Olkin) measure of sampling adequacy");
+            code.appendLine("from IPython.display import display, Markdown");
+            code.appendLine("from factor_analyzer.factor_analyzer import calculate_kmo");
+            code.appendLine("_kmo = calculate_kmo(vp_df)");
+            code.appendLine("display(Markdown('### KMO measure of sampling adequacy'))");
+            code.appendLine("display(pd.DataFrame(data={'Statistic ':_kmo[1]}, index=['KMO measure of sampling adequacy']))");
+
+            // Bartlett's test of sphericity
+            code.appendLine();
+            code.appendLine("# Bartlett's test of sphericity");
+            code.appendLine("from factor_analyzer.factor_analyzer import calculate_bartlett_sphericity");
+            code.appendLine("_bartlett = calculate_bartlett_sphericity(vp_df)");
+            code.appendLine("display(Markdown('### Bartlett\\'s test of sphericity'))");
+            code.appendLine("display(pd.DataFrame(data={'Chi-square statistic':_bartlett[0],'p-value':_bartlett[1]}, index=['Bartlett test of sphericity']))");
+
+            // Initial of Factor Analysis
+            code.appendLine();
+            code.appendLine("# Initial");
+            code.appendLine("from factor_analyzer import FactorAnalyzer");
+            code.appendFormatLine("_fa1 = FactorAnalyzer(n_factors=vp_df.shape[1], rotation=None, method='{0}', impute='{1}')", method, impute);
+            code.appendLine("_fa1.fit(vp_df)");
+
+            // Number of Factor
+            code.appendLine();
+            code.appendLine("# Number of Factor");
+            if (extract === 'eigenvalue') {
+                code.appendFormatLine("_nof = (_fa1.get_eigenvalues()[0] > {0}).sum()", eigenvalue);
+            } else if (extract === 'factor') {
+                code.appendFormatLine("_nof = {0}", factor);
+            }
+
+            // Unrotated
+            code.appendLine();
+            code.appendLine("# Un-rotated");
+            code.appendFormatLine("_fa2 = FactorAnalyzer(n_factors=_nof, rotation=None, method='{0}', impute='{1}')", method, impute);
+            code.appendLine("_fa2.fit(vp_df)");
+
+            // Rotated
+            code.appendLine();
+            code.appendLine("# Rotated");
+            code.appendFormatLine("_fa3 = FactorAnalyzer(n_factors=_nof, rotation={0}, method='{1}', impute='{2}')", rotation, method, impute);
+            code.append("_fa3.fit(vp_df)");
+
+            // Display option : Correlation Matrix
+            if (corrMatrix === true) {
+                code.appendLine();
+                code.appendLine();
+                code.appendLine("# Correlation matrix");
+                code.appendLine("display(Markdown('### Correlation matrix'))");
+                code.append("display(pd.DataFrame(data= _fa1.corr_ , index=vp_df.columns, columns=vp_df.columns).round(2))");
+            }
+
+            // Display option : Scree plot
+            if (screePlot === true) {
+                code.appendLine();
+                code.appendLine();
+                code.appendLine("# Scree plot");
+                code.appendLine("import warnings");
+                code.appendLine("with warnings.catch_warnings():");
+                code.appendLine("    warnings.simplefilter(action='ignore', category=Warning)");
+                code.appendLine("    plt.plot(_fa1.get_factor_variance()[1], 'o-')");
+                code.appendLine("    plt.title('Scree Plot')");
+                code.appendLine("    plt.xlabel('Factors')");
+                code.appendLine("    plt.ylabel('Eigenvalue')");
+                code.append("    plt.show()");
+            }
+
+            // Communalities
+            code.appendLine();
+            code.appendLine();
+            code.appendLine("# Communalities");
+            code.appendLine("display(Markdown('### Communalities'))");
+            code.appendLine("display(pd.DataFrame(data={'Initial':_fa1.get_communalities(),'Extraction':_fa2.get_communalities()},index=vp_df.columns).round(3))");
+
+            // Total variance explained
+            code.appendLine();
+            code.appendLine("# Total variance explained");
+            code.appendLine("# Initial Eigenvalues");
+            code.appendLine("_ss1 = pd.DataFrame(data=_fa1.get_factor_variance(),");
+            code.appendLine("                    index=[['Initial Eigenvalues' for i in range(3)],['Total','% of variance','Cumulative %']]).T");
+            code.appendLine("# Extraction sums of squared loadings");
+            code.appendLine("_ss2 = pd.DataFrame(data=_fa1.get_factor_variance(),");
+            code.appendLine("                    index=[['Extraction sums of squared loadings' for i in range(3)],['Total','% of variance','Cumulative %']]).T[:3]");
+            code.appendLine("# Rotation sums of squared loadings");
+            code.appendLine("_ss3 = pd.DataFrame(data=_fa3.get_factor_variance(),");
+            code.appendLine("                    index=[['Rotation sums of squared loadings' for i in range(3)],['Total','% of variance','Cumulative %']]).T");
+            code.appendLine("                    ");
+            code.appendLine("display(Markdown('### Total variance explained'))");
+            code.appendLine("display(pd.concat([_ss1,_ss2,_ss3], axis=1).round(3))");
+
+            // Factor Matrix
+            code.appendLine();
+            code.appendLine("# Factor matrix");
+            code.appendLine("display(Markdown('### Factor matrix'))");
+            code.appendLine("display(pd.DataFrame(data=_fa2.loadings_,index=vp_df.columns,");
+            code.appendLine("                     columns=list(range(_nof))).round(3))");
+
+            // Rotated Factor Matrix
+            code.appendLine();
+            code.appendLine("# Rotated factor matrix");
+            code.appendLine("display(Markdown('### Rotated factor matrix'))");
+            code.appendLine("display(pd.DataFrame(data=_fa3.loadings_,index=vp_df.columns,");
+            code.append("                     columns=list(range(_nof))).round(3))");
+
+            codeList.push(code.toString());
+
+            return codeList;
+        }
+
+    }
+
+    return FactorAnalysis;
+});
\ No newline at end of file
diff --git a/visualpython/js/m_stats/LogisticRegression.js b/visualpython/js/m_stats/LogisticRegression.js
new file mode 100644
index 00000000..87c2b470
--- /dev/null
+++ b/visualpython/js/m_stats/LogisticRegression.js
@@ -0,0 +1,170 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : LogisticRegression.js
+ *    Author          : Black Logic
+ *    Note            : Correlation Analysis
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 06. 02
+ *    Change Date     :
+ */
+
+//============================================================================
+// [CLASS] LogisticRegression
+//============================================================================
+define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/logisticRegression.html'),
+    'vp_base/js/com/com_util',
+    'vp_base/js/com/com_Const',
+    'vp_base/js/com/com_String',
+    'vp_base/js/com/com_generatorV2',
+    'vp_base/js/com/component/PopupComponent',
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/com/component/MultiSelector',
+    'vp_base/js/m_apps/Subset'
+], function(eqHTML, com_util, com_Const, com_String, com_generator, PopupComponent, DataSelector, MultiSelector, Subset) {
+
+    /**
+     * LogisticRegression
+     */
+    class LogisticRegression extends PopupComponent {
+        _init() {
+            super._init();
+            /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd'];
+
+            this.state = {
+                data: '',
+                dependent: '',
+                encoding: true,
+                independent: [],
+                showOdds: true,
+                multiCollinearity: true,
+                ...this.state
+            };
+
+            this.subsetEditor = null;
+            this.columnSelector = null;
+        }
+
+        _bindEvent() {
+            super._bindEvent();
+            /** Implement binding events */
+            var that = this;
+
+            $(this.wrapSelector('#data')).on('change', function() {
+                let data = $(this).val();
+                that.handleVariableChange(data);
+            });
+        }
+
+        handleVariableChange(data) {
+            this.state.data = data;
+            this.state.dependent = '';
+            this.state.independent = [];
+            // render column
+            com_generator.vp_bindColumnSource(this, 'data', ['dependent'], 'select', false, false);
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#independent'),
+                { mode: 'columns', parent: data, showDescription: false }
+            ); 
+        }
+
+        templateForBody() {
+            let page = $(eqHTML);
+            let that = this;
+
+            // generate dataselector
+            let dataSelector = new DataSelector({
+                pageThis: this, id: 'data', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                }
+            });
+            $(page).find('#data').replaceWith(dataSelector.toTagString());
+
+            return page;
+        }
+
+        render() {
+            super.render();
+            let that = this;
+
+            // render Subset
+            this.subsetEditor = new Subset({ 
+                pandasObject: '',
+                config: { name: 'Subset', category: this.name } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data'),
+                    pageThis: this,
+                    finish: function(code) {
+                        $(that.wrapSelector('#data')).val(code);
+                        that.handleVariableChange(code);
+                    }
+                });
+
+            // bind column if data exist
+            if (this.state.data !== '') {
+                com_generator.vp_bindColumnSource(this, 'data', ['dependent'], 'select', false, false);
+            }
+
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#independent'),
+                        { mode: 'columns', parent: this.state.data, selectedList: this.state.independent, showDescription: false });
+        }
+
+        generateCode() {
+            let { data, dependent, encoding, independent, showOdds, multiCollinearity } = this.state;
+            let codeList = [];
+            let code = new com_String();
+
+            let dependentValue = $(this.wrapSelector('#dependent option:selected')).text();
+            let independentMulti = this.columnSelector.getDataList();
+            this.state.independent = independentMulti;
+
+            // data declaration
+            code.appendFormatLine("vp_df = {0}.dropna().copy()", data);
+            if (encoding === true) {
+                code.appendFormatLine("vp_df['{0}'+'_EL'] = pd.Categorical(vp_df[{1}]).codes", dependentValue, dependent);
+                dependentValue = dependentValue + '_EL';
+            }
+            code.appendLine();
+            code.appendLine("# Logistic regression");
+            code.appendLine("from IPython.display import display");
+            code.appendLine("import statsmodels.formula.api as smf");
+            code.appendFormatLine("_model  = smf.logit('{0} ~ {1}', vp_df)", dependentValue, independentMulti.map(x=>x.name).join(' + '));
+            code.appendLine("_result = _model.fit()");
+            code.appendLine("print(_result.summary())");
+            code.appendLine("");
+            code.appendLine("# Multi-collinearity statistics");
+            code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+            code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+            if (showOdds === true) {
+                code.appendLine("_dfr['Odds'] = np.exp(_result.params)");
+                code.appendLine("_dfr['Lower(Odds)'] = np.exp(_result.conf_int()[0])");
+                code.appendLine("_dfr['Upper(Odds)'] = np.exp(_result.conf_int()[1])");
+            }
+            if (multiCollinearity === true) {
+                code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+            }
+            code.append("display(_dfr)");
+
+            return code.toString();
+        }
+
+    }
+
+    return LogisticRegression;
+});
\ No newline at end of file
diff --git a/visualpython/js/m_stats/NormTest.js b/visualpython/js/m_stats/NormTest.js
index ebb9dc74..e754c22d 100644
--- a/visualpython/js/m_stats/NormTest.js
+++ b/visualpython/js/m_stats/NormTest.js
@@ -3,7 +3,7 @@
  *    Description     : GUI-based Python code generator
  *    File Name       : NormTest.js
  *    Author          : Black Logic
- *    Note            : Norm test
+ *    Note            : Normality test
  *    License         : GNU GPLv3 with Visual Python special exception
  *    Date            : 2023. 05. 09
  *    Change Date     :
@@ -13,12 +13,15 @@
 // [CLASS] NormTest
 //============================================================================
 define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/normTest.html'),
     'vp_base/js/com/com_util',
     'vp_base/js/com/com_Const',
     'vp_base/js/com/com_String',
+    'vp_base/js/com/com_generatorV2',
     'vp_base/js/com/component/PopupComponent',
-    'vp_base/js/com/component/DataSelector'
-], function(com_util, com_Const, com_String, PopupComponent, DataSelector) {
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/m_apps/Subset'
+], function(nmHTML, com_util, com_Const, com_String, com_generator, PopupComponent, DataSelector, Subset) {
 
     /**
      * NormTest
@@ -27,36 +30,215 @@ define([
         _init() {
             super._init();
             /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd'];
+
+            this.state = {
+                testType: 'shapiro-wilk',
+                data0: '',
+                alterHypo: 'two-sided',
+                histogram: false,
+                boxplot: false,
+                qqplot: true,
+                ...this.state
+            };
+
+            this.subsetEditor = {};
         }
 
         _bindEvent() {
             super._bindEvent();
             /** Implement binding events */
             var that = this;
+
+            $(this.wrapSelector('#testType')).on('change', function() {
+                let testType = $(this).val();
+                that.state.testType = testType;
+
+                $(that.wrapSelector('.vp-st-option')).hide();
+                $(that.wrapSelector('.vp-st-option.' + testType)).show();
+            });
+
+            $(this.wrapSelector('#data0')).on('change', function() {
+                if (that.state.data0type === 'DataFrame') {
+                    // DataFrame
+                    that.state.var0 = '';
+                    $(that.wrapSelector('#var0')).prop('disabled', false);
+                    com_generator.vp_bindColumnSource(that, 'data0', ['var0'], 'select', false, false);
+                } else {
+                    // Series
+                    that.state.var0 = '';
+                    $(that.wrapSelector('#var0')).html('');
+                    $(that.wrapSelector('#var0')).prop('disabled', true);
+                }
+            });
         }
 
         templateForBody() {
-            /** Implement generating template */
-            return `This is sample.
-            <input type="text" id="sample" class="vp-state vp-input" readonly="" />`;
+            let page = $(nmHTML);
+            let that = this;
+
+            let dataSelector = new DataSelector({
+                pageThis: this, id: 'data0', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame', 'Series'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data0 = data;
+                    that.state.data0type = type;
+                    that.state.var0 = '';
+                    $(that.wrapSelector('#data0')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data0 = data;
+                    that.state.data0type = type;
+                    that.state.var0 = '';
+                    $(that.wrapSelector('#data0')).trigger('change');
+                }
+            });
+            $(page).find('#data0').replaceWith(dataSelector.toTagString());
+
+            return page;
         }
 
         render() {
             super.render();
+            let that = this;
 
-            let dataSelector = new DataSelector({
-                type: 'data',
-                pageThis: this,
-                id: 'sample',
-                finish: function() {
-                    ;
-                }
-            });
-            $(this.wrapSelector('#sample')).replaceWith(dataSelector.toTagString());
+            // render Subset
+            this.subsetEditor['data0'] = new Subset({ 
+                pandasObject: '',
+                config: { name: 'Subset', category: this.name } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data0'),
+                    pageThis: this,
+                    finish: function(code, state) {
+                        that.state.data0 = code;
+                        $(that.wrapSelector('#data0')).val(code);
+                        that.state.data0type = state.returnType;
+                        $(that.wrapSelector('#data0')).trigger('change');
+                    }
+                });
+
+            // control display option
+            $(this.wrapSelector('.vp-st-option')).hide();
+            $(this.wrapSelector('.vp-st-option.' + this.state.testType)).show();
         }
 
         generateCode() {
-            return "print('sample code')";
+            let { testType, data0, data0type, var0, alterHypo, histogram, boxplot, qqplot } = this.state;
+            let codeList = [];
+            let code = new com_String();
+
+            // test type label
+            let testTypeLabel = $(this.wrapSelector('#testType option:selected')).text();
+            code.appendFormatLine('# {0}', testTypeLabel);
+
+            // variable declaration
+            code.appendFormatLine("vp_df = {0}.dropna().copy()", data0);
+
+            let dataVar = 'vp_df';
+            if (var0 !== '') {
+                dataVar += com_util.formatString("[{0}]", var0);
+            }
+
+            switch (testType) {
+                case 'shapiro-wilk':
+                    // 1. Shapiro-wilk test
+                    code.appendLine();
+                    code.appendLine("# Normality test (Shapiro-Wilk)");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("from scipy import stats");
+                    code.appendFormatLine("_res = stats.shapiro({0})", dataVar);
+                    code.appendLine("display(Markdown('### Normality test (Shapiro-Wilk)'))");
+                    code.append("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue},index=['Normality test (Shapiro-Wilk)']))");
+                    break;
+                case 'anderson-darling':
+                    // 1. Anderson-Darling test
+                    code.appendLine();
+                    code.appendLine("# Normality test (Anderson-Darling)");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("from scipy import stats");
+                    code.appendFormatLine("_res = stats.anderson({0})", dataVar);
+                    code.appendLine("display(Markdown('### Normality test (Anderson-Darling)'))");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':[_res.statistic],'Critical values':[_res.critical_values],");
+                    code.appendLine("                           'Significance level(%)':[_res.significance_level]},");
+                    code.append("                     index=['Normality test (Anderson-Darling)']))");
+                    break;
+                case 'kolmogorov-smirnov':
+                    // 1. Kolmogorov-Smirnov test
+                    code.appendLine();
+                    code.appendLine("# Normality test (Kolmogorov-Smirnov)");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("from scipy import stats");
+                    code.appendFormatLine("_res = stats.kstest({0}, 'norm', alternative='{1}')", dataVar, alterHypo);
+                    code.appendLine("display(Markdown('### Normality test (Kolmogorov-Smirnov)'))");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue},");
+                    code.append("                     index=['Normality test (Kolmogorov-Smirnov)']))");
+                    break;
+                case 'dagostino-pearson':
+                    // 1. D Agostino and Pearson test
+                    code.appendLine();
+                    code.appendLine("# Normality test (D Agostino and Pearson)");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("from scipy import stats");
+                    code.appendFormatLine("_res = stats.normaltest({0})", dataVar);
+                    code.appendLine("display(Markdown('### Normality test (D Agostino and Pearson)'))");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue},");
+                    code.append("                     index=['Normality test (D Agostino and Pearson)']))");
+                    break;
+                case 'jarque-bera':
+                    // 1. Jarque-Bera test
+                    code.appendLine();
+                    code.appendLine("# Normality test (Jarque-Bera)");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("from scipy import stats");
+                    code.appendFormatLine("_res = stats.jarque_bera({0})", dataVar);
+                    code.appendLine("display(Markdown('### Normality test (Jarque-Bera)'))");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue},");
+                    code.append("                     index=['Normality test (Jarque-Bera)']))");
+                    break;
+            }
+
+            // Display option
+            if (histogram === true || boxplot === true || qqplot === true) {
+                code.appendLine();
+                code.appendLine();
+                code.appendLine("# Charts");
+                code.appendLine("import seaborn as sns");
+                code.appendLine("import warnings");
+                code.appendLine("with warnings.catch_warnings():");
+                code.append("    warnings.simplefilter(action='ignore', category=Warning)");
+                let displayNum = 1;
+                if (histogram === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendFormatLine("    plt.subplot(2,2,{0})", displayNum++);
+                    code.appendFormatLine("    sns.histplot({0}, stat='density', kde=True)", dataVar);
+                    code.append("    plt.title('Histogram')");
+                }
+                if (boxplot === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendFormatLine("    plt.subplot(2,2,{0})", displayNum++);
+                    code.appendFormatLine("    sns.boxplot(y={0})", dataVar);
+                    code.append("    plt.title('Boxplot')");
+                }
+    
+                if (qqplot === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendFormatLine("    plt.subplot(2,2,{0})", displayNum);
+                    code.appendFormatLine("    stats.probplot({0}, plot=plt)", dataVar);
+                    code.append("    plt.title('Q-Q Plot')");
+                }
+                code.appendLine();
+                code.appendLine();
+                code.appendLine("    plt.tight_layout()");
+                code.append("    plt.show()");
+            }
+
+            return code.toString();
         }
 
     }
diff --git a/visualpython/js/m_stats/ProbDist.js b/visualpython/js/m_stats/ProbDist.js
index e89b8c9c..ff110664 100644
--- a/visualpython/js/m_stats/ProbDist.js
+++ b/visualpython/js/m_stats/ProbDist.js
@@ -13,12 +13,16 @@
 // [CLASS] ProbDist
 //============================================================================
 define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/probDist.html'),
+    __VP_CSS_LOADER__('vp_base/css/m_stats/probDist'),
     'vp_base/js/com/com_util',
     'vp_base/js/com/com_Const',
     'vp_base/js/com/com_String',
+    'vp_base/js/com/com_generatorV2',
+    'vp_base/data/m_stats/statsLibrary',
     'vp_base/js/com/component/PopupComponent',
     'vp_base/js/com/component/DataSelector'
-], function(com_util, com_Const, com_String, PopupComponent, DataSelector) {
+], function(pdHTML, pdCss, com_util, com_Const, com_String, com_generator, STATS_LIBRARIES, PopupComponent, DataSelector) {
 
     /**
      * ProbDist
@@ -27,36 +31,321 @@ define([
         _init() {
             super._init();
             /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd', 'plt'];
+
+            this.state = {
+                distType: 'normal',
+                userOption: '',
+                action: 'random-number',
+                // random-number
+                size: 10000,
+                randomState: '',
+                allocateTo: 'samples',
+                sampledDist: true,
+                // distribution-plot
+                probDensityFunc: true,
+                probMassFunc: true,
+                cumDistFunc: true,
+                // stats-to-pvalue
+                stats: '',
+                pAlter: 'two-sided',
+                // pvalue-to-stats
+                pvalue: '',
+                statsAlter: 'two-sided',
+                ...this.state
+            };
+
+            this.distList = [
+                {
+                    label: 'Discrete probability distribution',
+                    child: ['bernoulli', 'binomial', 'multinomial']
+                }, 
+                {
+                    label: 'Continuous probability distribution',
+                    child: ['uniform','normal','beta','gamma','studentst','chi2','f','dirichlet','multivariate_normal']
+                }
+            ];
         }
 
         _bindEvent() {
             super._bindEvent();
             /** Implement binding events */
             var that = this;
+
+            $(this.wrapSelector('#distType')).on('change', function() {
+                let distType = $(this).val();
+                that.state.distType = distType;
+                $(that.wrapSelector('.vp-pd-dist-option-box')).html(that.templateForOption(distType));
+
+                $(that.wrapSelector('.vp-pd-display-option')).hide();
+                // show/hide display option
+                if (that.distList[0].child.includes(distType)) {
+                    // discrete option
+                    $(that.wrapSelector('.vp-pd-display-option.dist')).show();
+
+                    // hide continuous action
+                    if (that.state.action === 'stats-to-pvalue' || that.state.action === 'pvalue-to-stats') {
+                        $(that.wrapSelector('#action')).val('random-number');
+                        $(that.wrapSelector('#action')).trigger('change');
+                    }
+                } else {
+                    // continuous option
+                    $(that.wrapSelector('.vp-pd-display-option.cont')).show();
+                }
+
+                // show install button
+                if (STATS_LIBRARIES[distType].install != undefined) {
+                    $(that.wrapSelector('#vp_installLibrary')).show();
+                } else {
+                    $(that.wrapSelector('#vp_installLibrary')).hide();
+                }
+            });
+
+            $(this.wrapSelector('#action')).on('change', function() {
+                let action = $(this).val();
+                that.state.action = action;
+
+                $(that.wrapSelector('.vp-pd-action-box')).hide();
+                $(that.wrapSelector('.vp-pd-action-box.' + action)).show();
+
+                $(that.wrapSelector('.vp-pd-display-option')).hide();
+                // show/hide display option
+                if (that.distList[0].child.includes(that.state.distType)) {
+                    // discrete option
+                    $(that.wrapSelector('.vp-pd-display-option.dist')).show();
+                } else {
+                    // continuous option
+                    $(that.wrapSelector('.vp-pd-display-option.cont')).show();
+                }
+            });
         }
 
         templateForBody() {
-            /** Implement generating template */
-            return `This is sample.
-            <input type="text" id="sample" class="vp-state vp-input" readonly="" />`;
+            let page = $(pdHTML);
+            let that = this;
+
+            //================================================================
+            // Distribution type creation
+            //================================================================
+            // dist types
+            let distTypeTag = new com_String();
+            this.distList.forEach(distObj => {
+                let { label, child } = distObj;
+                let distOptionTag = new com_String();
+                child && child.forEach(opt => {
+                    let optConfig = STATS_LIBRARIES[opt];
+                    let selectedFlag = '';
+                    if (opt == that.state.distType) {
+                        selectedFlag = 'selected';
+                    }
+                    distOptionTag.appendFormatLine('<option value="{0}" {1}>{2}</option>',
+                                    opt, selectedFlag, optConfig.name);
+                })
+                distTypeTag.appendFormatLine('<optgroup label="{0}">{1}</optgroup>', 
+                    label, distOptionTag.toString());
+            });
+            $(page).find('#distType').html(distTypeTag.toString());
+
+            // render option page
+            $(page).find('.vp-pd-dist-option-box').html(this.templateForOption(this.state.distType));
+
+            // control display option
+            $(this.wrapSelector('.vp-pd-display-option')).hide();
+            // show/hide display option
+            if (this.distList[0].child.includes(this.state.distType)) {
+                // discrete option
+                $(this.wrapSelector('.vp-pd-display-option.dist')).show();
+            } else {
+                // continuous option
+                $(this.wrapSelector('.vp-pd-display-option.cont')).show();
+            }
+
+            return page;
+        }
+
+        templateForOption(distType) {
+            let config = STATS_LIBRARIES[distType];
+            let state = this.state;
+
+            let optBox = new com_String();
+            // render tag
+            config.options.forEach(opt => {
+                optBox.appendFormatLine('<label for="{0}" class="{1}" title="{2}">{3}</label>'
+                    , opt.name, (opt.required===true?'vp-orange-text':''), opt.name, com_util.optionToLabel(opt.name));
+                let content = com_generator.renderContent(this, opt.component[0], opt, state);
+                optBox.appendLine(content[0].outerHTML);
+            });
+            // render user option
+            optBox.appendFormatLine('<label for="{0}">{1}</label>', 'userOption', 'User option');
+            optBox.appendFormatLine('<input type="text" class="vp-input vp-state" id="{0}" placeholder="{1}" value="{2}"/>',
+                                        'userOption', 'key=value, ...', this.state.userOption);
+            return optBox.toString();
         }
 
         render() {
             super.render();
 
-            let dataSelector = new DataSelector({
+            let allocateSelector = new DataSelector({
                 type: 'data',
                 pageThis: this,
-                id: 'sample',
+                id: 'allocatedTo',
+                classes: 'vp-input vp-state',
+                placeholder: '_res',
                 finish: function() {
                     ;
                 }
             });
-            $(this.wrapSelector('#sample')).replaceWith(dataSelector.toTagString());
+            $(this.wrapSelector('#allocatedTo')).replaceWith(allocateSelector.toTagString());
         }
 
         generateCode() {
-            return "print('sample code')";
+            this.config.checkModules = ['pd'];
+            let { 
+                distType, userOption, action, 
+                size, randomState, allocateTo, sampledDist, 
+                probDensityFunc, probMassFunc, cumDistFunc,
+                stats, pAlter,
+                pvalue, statsAlter
+            } = this.state;
+
+            let codeList = [];
+            let code = new com_String();
+            /**
+             * Model Creation
+             */
+            let config = STATS_LIBRARIES[distType];
+            let label = config.name;
+            code.appendLine(config.import);
+            code.appendLine();
+
+            // model code
+            let modelCode = config.code;
+            modelCode = com_generator.vp_codeGenerator(this, config, this.state, (userOption != ''? ', ' + userOption : ''));
+            code.append(modelCode);
+
+            switch (action) {
+                case 'random-number':
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendFormatLine("# Generate random numbers ({0})", label);
+                    code.appendLine("from IPython.display import display");
+                    code.appendFormat('{0} = _rv.rvs(size={1}', allocateTo, size);
+                    if (randomState !== '') {
+                        code.appendFormat(", random_state={0}", randomState);
+                    }
+                    code.appendLine(')');
+                    code.appendFormat("display({0})", allocateTo);
+
+                    if (sampledDist === true) {
+                        this.addCheckModules('plt');
+                        this.addCheckModules('sns');
+
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendFormatLine("# Sample distribution ({0})", label);
+                        code.appendLine("import warnings");
+                        code.appendLine("with warnings.catch_warnings():");
+                        code.appendLine("    warnings.simplefilter(action='ignore', category=Warning)");
+                        code.appendFormatLine("    sns.histplot({0}, stat='density', kde=True)", allocateTo);
+                        code.appendFormatLine("    plt.title('Generate random numbers: {0}')", label.replace("'", "\\'"));
+                        code.appendLine("    plt.xlabel('$x$')");
+                        code.append("    plt.show()");
+                    }
+                    break;
+                case 'distribution-plot':
+                    if (this.distList[0].child.includes(distType)) {
+                        if (probMassFunc === true) {
+                            this.addCheckModules('np');
+                            this.addCheckModules('plt');
+
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendFormatLine("# Probability mass function ({0})", label);
+                            code.appendLine("plt.bar([0,1], _rv.pmf([0,1]))");
+                            code.appendFormatLine("plt.title('Probability mass function: {0}')", label.replace("'", "\\'"));
+                            code.appendLine("plt.xlim(-1, 2)");
+                            code.appendLine("plt.ylim(0, 1)");
+                            code.appendLine("plt.xticks([0, 1], ['x=0', 'x=1'])");
+                            code.appendLine("plt.xlabel('$x$')");
+                            code.appendLine("plt.ylabel('$p(x)$')");
+                            code.append("plt.show()");
+                        }
+                    } else {
+                        if (probDensityFunc === true || cumDistFunc === true) {
+                            code.appendLine();
+                            code.append("x = np.linspace(-5, 5, 100)");
+                            if (probDensityFunc === true) {
+                                this.addCheckModules('np');
+                                this.addCheckModules('plt');
+    
+                                code.appendLine();
+                                code.appendLine();
+                                code.appendFormatLine("# Probability density function ({0})", label);
+                                code.appendLine("plt.plot(x, _rv.pdf(x))");
+                                code.appendFormatLine("plt.title('Probability density function: {0}')", label.replace("'", "\\'"));
+                                code.appendLine("plt.xlabel('$x$')");
+                                code.appendLine("plt.ylabel('$p(x)$')");
+                                code.append("plt.show()");
+                            }
+                            if (cumDistFunc === true) {
+                                this.addCheckModules('np');
+                                this.addCheckModules('plt');
+                                
+                                code.appendLine();
+                                code.appendLine();
+                                code.appendFormatLine("# Cumulative distribution function ({0})", label);
+                                code.appendLine("import warnings");
+                                code.appendLine("with warnings.catch_warnings():");
+                                code.appendLine("    _x = np.linspace(-5, 5, 100)");
+                                code.appendLine("    plt.plot(_x, _rv.cdf(_x))");
+                                code.appendLine();
+                                code.appendFormatLine("    plt.title('Cumulative distribution function: {0}')", label.replace("'", "\\'"));
+                                code.appendLine("    plt.xlabel('$x$')");
+                                code.appendLine("    plt.ylabel('$F(x)$')");
+                                code.append("    plt.show()");
+                            }
+                        }
+                    }
+                    break;
+                case 'stats-to-pvalue':
+                    if (pAlter === 'one-sided') {
+                        // one-sided
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Proportional values");
+                        code.appendFormatLine("p_value = _rv.sf(abs({0}))", stats);
+                        code.append("p_value");
+                    } else {
+                        // two-sided
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Proportional values");
+                        code.appendFormatLine("p_value = _rv.sf(abs({0}))*2", stats);
+                        code.append("p_value");
+                    }
+                    break;
+                case 'pvalue-to-stats': 
+                    if (statsAlter === 'one-sided') {
+                        // one-sided
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Statistic");
+                        code.appendFormatLine("statistic = _rv.isf({0})", pvalue);
+                        code.append("statistic");
+                    } else {
+                        // two-sided
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Statistic");
+                        code.appendFormatLine("statistic = _rv.isf({0}/2)", pvalue);
+                        code.append("statistic");
+                    }
+                    break;
+            }
+            codeList.push(code.toString());
+                
+            return codeList;
         }
 
     }
diff --git a/visualpython/js/m_stats/Regression.js b/visualpython/js/m_stats/Regression.js
new file mode 100644
index 00000000..7744e83f
--- /dev/null
+++ b/visualpython/js/m_stats/Regression.js
@@ -0,0 +1,738 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : Regression.js
+ *    Author          : Black Logic
+ *    Note            : Equal Variance test
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 05. 09
+ *    Change Date     :
+ */
+
+//============================================================================
+// [CLASS] EqualVarTest
+//============================================================================
+define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/regression.html'),
+    __VP_CSS_LOADER__('vp_base/css/m_stats/regression'),
+    'vp_base/js/com/com_util',
+    'vp_base/js/com/com_Const',
+    'vp_base/js/com/com_String',
+    'vp_base/js/com/com_generatorV2',
+    'vp_base/js/com/component/PopupComponent',
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/com/component/MultiSelector',
+    'vp_base/js/m_apps/Subset'
+], function(eqHTML, rgCss, com_util, com_Const, com_String, com_generator, PopupComponent, DataSelector, MultiSelector, Subset) {
+
+    /**
+     * Regression
+     */
+    class Regression extends PopupComponent {
+        _init() {
+            super._init();
+            /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd'];
+
+            this.state = {
+                testType: 'simple',
+                // Data selection
+                data: '',
+                dataType: '',
+                dependent: '',
+                independent: '',
+                independentMulti: [],
+                moderated: '',
+                mediated: '',
+                // options
+                categorical: [],
+                method: 'enter',
+                meanCentering: true,
+                sobelTest: true,
+                // Multi-collinearity
+                multiCollinearity: true,
+                // Residual option
+                statistics: false,
+                normTest: true,
+                histogram: true,
+                scatterplot: true,
+                ...this.state
+            };
+
+            this.colBindList = ['dependent', 'independent', 'moderated', 'mediated'];
+
+            this.subsetEditor = null;
+            this.columnSelector = null;
+        }
+
+        _bindEvent() {
+            super._bindEvent();
+            /** Implement binding events */
+            var that = this;
+
+            // change test type
+            $(this.wrapSelector('#testType')).on('change', function() {
+                let testType = $(this).val();
+                that.state.testType = testType;
+
+                $(that.wrapSelector('.vp-st-option')).hide();
+                $(that.wrapSelector('.vp-st-option.' + testType)).show();
+
+                // render variable selector
+                that.columnSelector = new MultiSelector(that.wrapSelector('#independentBox'),
+                    { 
+                        mode: 'columns', parent: that.state.data, showDescription: false,
+                        change: function(type, list) {
+                            that._handleMultiColumnChange(type, list);
+                        } 
+                    });
+            });
+
+            // data change
+            $(this.wrapSelector('#data')).on('change', function() {
+                let data = $(this).val();
+                that.handleVariableChange(data);
+            });
+        }
+
+        handleVariableChange(data) {
+            this.state.data = data;
+            this.state.independentMulti = [];
+            let that = this;
+            // bind column sources
+            if (this.state.dataType === 'DataFrame') {
+                // DataFrame
+                this.colBindList && this.colBindList.forEach(id => {
+                    that.state[id] = '';
+                    $(that.wrapSelector('#' + id)).prop('disabled', false);
+                });
+                com_generator.vp_bindColumnSource(this, 'data', this.colBindList, 'select', false, false);
+            } else {
+                // Others
+                this.colBindList && this.colBindList.forEach(id => {
+                    that.state[id] = '';
+                    $(that.wrapSelector('#' + id)).html('');
+                    $(that.wrapSelector('#' + id)).prop('disabled', true);
+                });
+            }
+
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#independentBox'),
+                {   
+                    mode: 'columns', parent: data, showDescription: false, 
+                    change: function(type, list) {
+                        that._handleMultiColumnChange(type, list);
+                    }
+                }
+            );
+        }
+
+        _handleMultiColumnChange(type, list) {
+            let $newCateBox = $('<div class="vp-categorical-box"></div>');
+            let that = this;
+            list && list.forEach(item => {
+                let checkedStr = 'checked';
+                if ($(that.wrapSelector('.vp-categorical-box input[data-name="' + item.name + '"]')).length > 0) {
+                    $(that.wrapSelector('.vp-categorical-box input[data-name="' + item.name + '"]')).prop('checked')?'checked':'';
+                }
+                $newCateBox.append(`<label>
+                    <input type="checkbox" data-name="${item.name}" data-code="${item.code}" class="vp-state" ${checkedStr}><span>${item.name}</span>
+                </label>`);
+            });
+            $(this.wrapSelector('.vp-categorical-box')).replaceWith($newCateBox);
+        }
+
+        templateForBody() {
+            let page = $(eqHTML);
+            let that = this;
+
+            let dataSelector = new DataSelector({
+                pageThis: this, id: 'data', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data = data;
+                    that.state.dataType = type;
+                    $(that.wrapSelector('#data')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data = data;
+                    that.state.dataType = type;
+                    $(that.wrapSelector('#data')).trigger('change');
+                }
+            });
+            $(page).find('#data').replaceWith(dataSelector.toTagString());
+
+            // depend on test type
+            $(page).find('.vp-st-option').hide();
+            $(page).find('.vp-st-option.' + this.state.testType).show();
+
+            return page;
+        }
+
+        render() {
+            super.render();
+            let that = this;
+
+            // render Subset
+            this.subsetEditor = new Subset({ 
+                pandasObject: this.state.data,
+                config: { name: 'Subset', category: this.name } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data'),
+                    pageThis: this,
+                    finish: function(code, state) {
+                        that.state.data = code;
+                        $(that.wrapSelector('#data')).val(code);
+                        that.state.dataType = state.returnType;
+                        $(that.wrapSelector('#data')).trigger('change');
+                    }
+                });
+
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#indenpendentBox'),
+                        { mode: 'columns', parent: this.state.data, selectedList: this.state.independentMulti.map(x=>x.code), showDescription: false }
+                    );
+            
+            // bind column if data exist
+            if (this.state.data !== '') {
+                com_generator.vp_bindColumnSource(this, 'data', this.colBindList, 'select', false, false);
+            }
+
+            // control display option
+            $(this.wrapSelector('.vp-st-option')).hide();
+            $(this.wrapSelector('.vp-st-option.' + this.state.testType)).show();
+        }
+
+        generateCode() {
+            let { testType,
+                // Data selection
+                data, dataType, dependent, independent, independentMulti, moderated, mediated,
+                // options
+                method, meanCentering, sobelTest,
+                // Multi-collinearity
+                multiCollinearity,
+                // Residual option
+                statistics, normTest, histogram, scatterplot
+            } = this.state;
+            let codeList = [];
+            let code = new com_String();
+            let that = this;
+            let lastModelNum = 0;
+
+            // Commentary
+            let testTypeLabel = $(this.wrapSelector('#testType option:selected')).text();
+            let methodLabel = $(this.wrapSelector('#method option:selected')).text();
+            if (testType === 'multiple') {
+                code.appendFormatLine("# {0} > Method: {1}", testTypeLabel, methodLabel);
+            } else {
+                code.appendFormatLine("# {0}", testTypeLabel);
+            }
+
+            // data declaration
+            code.appendFormatLine("vp_df = {0}.dropna().copy()", data);
+            
+            // data and columns
+            let dependentValue = $(this.wrapSelector('#dependent option:selected')).text();
+            let independentValue = $(this.wrapSelector('#independent option:selected')).text();
+            let moderatedValue = $(this.wrapSelector('#moderated option:selected')).text();
+            let mediatedValue = $(this.wrapSelector('#mediated option:selected')).text();
+            independentMulti = this.columnSelector.getDataList();
+            this.state.independentMulti = independentMulti;
+
+            switch (testType) {
+                case 'simple':
+                    // 1. Simple
+                    code.appendLine();
+                    code.appendLine("# Simple linear regression");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("import statsmodels.formula.api as smf");
+                    code.appendLine("# Model - Dependent variable ~ Independent variable");
+                    code.appendFormatLine("_model  = smf.ols('{0} ~ {1}', vp_df)", dependentValue, independentValue);
+                    code.appendLine("_result = _model.fit()");
+                    code.appendLine("display(Markdown('### Model - Dependent variable ~ Independent variable'))");
+                    code.append("print(_result.summary())");
+                    // Multi-collinearity statistics
+                    if (multiCollinearity === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Multi-collinearity statistics");
+                        code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                        code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                        code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                        code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                        code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                        code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                        code.append("display(_dfr)");
+                    }
+                    break;
+                case 'multiple':
+                    // 2. Multiple
+                    code.appendLine();
+                    if (method === 'enter') {
+                        code.appendLine("# Model - Dependent variable ~ Independent variable");
+                        code.appendLine("from IPython.display import display, Markdown");
+                        code.appendLine("import statsmodels.formula.api as smf");
+                        code.appendFormatLine("_model  = smf.ols('{0} ~ {1}', vp_df)", dependentValue, independentMulti.map(x => x.name).join(' + '));
+                        code.appendLine("_result = _model.fit()");
+                        code.appendLine("display(Markdown('### Model - Dependent variable ~ Independent variable'))");
+                        code.append("print(_result.summary())");
+                    } else if (method === 'stepwise') {
+                        // Inner function : vp_stepwise_select
+                        this.addCheckModules('statsmodels.api');
+                        this.addCheckModules('vp_stepwise_select');
+
+                        code.appendFormatLine("_selected_stepwise = vp_stepwise_select(vp_df[[{0}]], vp_df[{1}])", independentMulti.map(x => x.code).join(','), dependent);
+                        code.appendLine("");
+                        code.appendLine("# Model 1 - Dependent variable ~ Independent variable");
+                        code.appendLine("from IPython.display import display, Markdown");
+                        code.appendLine("import statsmodels.api as sm");
+                        code.appendFormatLine("_model  = sm.OLS(vp_df[{0}], sm.add_constant(vp_df[_selected_stepwise[0]]))", dependent);
+                        code.appendLine("_result = _model.fit()");
+                        code.appendLine("display(Markdown('### Model 1 - Dependent variable ~ Independent variable'))");
+                        code.append("print(_result.summary())");
+                        if (multiCollinearity === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Model 1 - Multi-collinearity statistics");
+                            code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                            code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                            code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                            code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                            code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                            code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                            code.append("display(_dfr)");
+                        }
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 2 - Dependent variable ~ Stepwised variable");
+                        code.appendLine("import statsmodels.api as sm");
+                        code.appendFormatLine("_model  = sm.OLS(vp_df[{0}], sm.add_constant(vp_df[_selected_stepwise]))", dependent);
+                        code.appendLine("_result = _model.fit()");
+                        code.appendLine("display(Markdown('### Model 2 - Dependent variable ~ Stepwised variable'))");
+                        code.append("print(_result.summary())");
+                        if (multiCollinearity === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Model 2 - Multi-collinearity statistics");
+                            code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                            code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                            code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                            code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                            code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                            code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                            code.append("display(_dfr)");
+                        }
+                        // set last model number
+                        lastModelNum = 2;
+                    } else if (method === 'backward') {
+                        // Inner function : vp_backward_select
+                        this.addCheckModules('statsmodels.api');
+                        this.addCheckModules('vp_backward_select');
+
+                        code.appendFormatLine("_selected_backward = vp_backward_select(vp_df[[{0}]], vp_df[{1}])", independentMulti.map(x => x.code).join(','), dependent);
+                        code.appendLine();
+                        code.appendLine("# Model 1 - Dependent variable ~ Independent variable");
+                        code.appendLine("from IPython.display import display, Markdown");
+                        code.appendLine("import statsmodels.api as sm");
+                        code.appendFormatLine("_model  = sm.OLS(vp_df[{0}], sm.add_constant(vp_df[[{1}]]))", dependent, independentMulti.map(x => x.code).join(','));
+                        code.appendLine("_result = _model.fit()");
+                        code.appendLine("display(Markdown('### Model 1 - Dependent variable ~ Independent variable'))");
+                        code.append("print(_result.summary())");
+                        if (multiCollinearity === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Model 1 - Multi-collinearity statistics");
+                            code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                            code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                            code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                            code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                            code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                            code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                            code.append("display(_dfr)");
+                        }
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 2 - Dependent variable ~ Backward variable");
+                        code.appendLine("import statsmodels.api as sm");
+                        code.appendFormatLine("_model  = sm.OLS(vp_df[{0}], sm.add_constant(vp_df[_selected_backward]))", dependent);
+                        code.appendLine("_result = _model.fit()");
+                        code.appendLine("display(Markdown('### Model 2 - Dependent variable ~ Backward variable'))");
+                        code.append("print(_result.summary())");
+                        if (multiCollinearity === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Model 2 - Multi-collinearity statistics");
+                            code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                            code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                            code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                            code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                            code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                            code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                            code.append("display(_dfr)");
+                        }
+                        // set last model number
+                        lastModelNum = 2;
+                    } else if (method === 'forward') {
+                        // Inner function : vp_forward_select
+                        this.addCheckModules('statsmodels.api');
+                        this.addCheckModules('vp_forward_select');
+
+                        code.appendFormatLine("_selected_forward = vp_forward_select(vp_df[[{0}]], vp_df[{1}])", independentMulti.map(x => x.code).join(','), dependent);
+                        code.appendLine();
+                        code.appendLine("# Model 1 - Dependent variable ~ Independent variable");
+                        code.appendLine("from IPython.display import display, Markdown");
+                        code.appendLine("import statsmodels.api as sm");
+                        code.appendFormatLine("_model  = sm.OLS(vp_df[{0}], sm.add_constant(vp_df[_selected_forward[0]]))", dependent);
+                        code.appendLine("_result = _model.fit()");
+                        code.appendLine("display(Markdown('### Model 1 - Dependent variable ~ Independent variable'))");
+                        code.append("print(_result.summary())");
+                        if (multiCollinearity === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Model 1 - Multi-collinearity statistics");
+                            code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                            code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                            code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                            code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                            code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                            code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                            code.append("display(_dfr)");
+                        }
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 2 - Dependent variable ~ Forward variable");
+                        code.appendLine("import statsmodels.api as sm");
+                        code.appendFormatLine("_model  = sm.OLS(vp_df[{0}], sm.add_constant(vp_df[_selected_forward]))", dependent);
+                        code.appendLine("_result = _model.fit()");
+                        code.appendLine("display(Markdown('### Model 2 - Dependent variable ~ Forward variable'))");
+                        code.append("print(_result.summary())");
+                        if (multiCollinearity === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendLine("# Model 2 - Multi-collinearity statistics");
+                            code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                            code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                            code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                            code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                            code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                            code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                            code.append("display(_dfr)");
+                        }
+                        // set last model number
+                        lastModelNum = 2;
+                    }
+                    break;
+                case 'hierarchical':
+                    // 3. Hierarchical
+                    for (let i = 0; i < independentMulti.length; i++) {
+                        if (i === 0) {
+                            code.appendLine();
+                        } else {
+                            code.appendLine();
+                            code.appendLine();
+                        }
+                        code.appendFormatLine("# Model {0} - Hierarchical linear regression", (i + 1));
+                        code.appendLine("from IPython.display import display, Markdown");
+                        code.appendLine("import statsmodels.formula.api as smf");
+                        code.appendFormatLine("_model  = smf.ols('{0} ~ {1}', vp_df)", dependentValue, independentMulti.slice(0, i + 1).map(x => x.name).join(' + '));
+                        code.appendLine("_result = _model.fit()");
+                        code.appendFormatLine("display(Markdown('### Model {0} - Dependent variable ~ Independent variable'))", (i + 1));
+                        code.append("print(_result.summary())");
+                        if (multiCollinearity === true) {
+                            code.appendLine();
+                            code.appendLine();
+                            code.appendFormatLine("# Model {0} - Multi-collinearity statistics", (i + 1));
+                            code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                            code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                            code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                            code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                            code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                            code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                            code.append("display(_dfr)");
+                        }
+                    }
+                    lastModelNum = independentMulti.length;
+                    break;
+                case 'moderated':
+                    // 4. Moderated
+                    // Mean centering
+                    if (meanCentering === true) {
+                        code.appendLine();
+                        code.appendLine("# Mean Centering ");
+                        independentValue = com_util.formatString("{0}_MC", independentValue);
+                        moderatedValue = com_util.formatString("{0}_MC", moderatedValue);
+                        code.appendFormatLine("vp_df['{0}']   = vp_df[{1}] - vp_df[{2}].mean()", independentValue, independent, independent);
+                        code.appendFormatLine("vp_df['{0}'] = vp_df[{1}] - vp_df[{2}].mean()", moderatedValue, moderated, moderated);
+                    }
+                    // Model 1 to 3
+                    code.appendLine();
+                    code.appendLine("# Model 1 - Dependent variable ~ Independent variable");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("import statsmodels.formula.api as smf");
+                    code.appendFormatLine("_model  = smf.ols('{0} ~ {1}', vp_df)", dependentValue, independentValue);
+                    code.appendLine("_result = _model.fit()");
+                    code.appendLine("display(Markdown('### Model 1 - Dependent variable ~ Independent variable'))");
+                    code.append("print(_result.summary())");
+                    if (multiCollinearity === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 1 - Multi-collinearity statistics");
+                        code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                        code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                        code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                        code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                        code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                        code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                        code.append("display(_dfr)");
+                    }
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# Model 2 - Dependent variable ~ Independent variable + Moderated variable");
+                    code.appendLine("import statsmodels.formula.api as smf");
+                    code.appendFormatLine("_model  = smf.ols('{0} ~ {1} + {2}', vp_df)", dependentValue, independentValue, moderatedValue);
+                    code.appendLine("_result = _model.fit()");
+                    code.appendLine("display(Markdown('### Model 2 - Dependent variable ~ Independent variable + Moderated variable'))");
+                    code.append("print(_result.summary())");
+                    if (multiCollinearity === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 2 - Multi-collinearity statistics");
+                        code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                        code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                        code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                        code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                        code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                        code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                        code.append("display(_dfr)");
+                    }
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# Model 3 - Dependent variable ~ Independent variable + Moderated variable +Independent:Moderated");
+                    code.appendLine("import statsmodels.formula.api as smf");
+                    code.appendFormatLine("_model  = smf.ols('{0} ~ {1} + {2} + {3}:{4}', vp_df)", dependentValue, independentValue, moderatedValue, independentValue, moderatedValue);
+                    code.appendLine("_result = _model.fit()");
+                    code.appendLine("display(Markdown('### Model 3 - Dependent variable ~ Independent variable + Moderated variable +Independent:Moderated'))");
+                    code.append("print(_result.summary())");
+                    if (multiCollinearity === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 3 - Multi-collinearity statistics");
+                        code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                        code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                        code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                        code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                        code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                        code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                        code.append("display(_dfr)");
+                    }
+                    // set last model number
+                    lastModelNum = 3;
+                    break;
+                case 'mediated':
+                    // 5. Mediated
+                    if (sobelTest === true) {
+                        this.addCheckModules('scipy.stats');
+                        this.addCheckModules('vp_sobel');
+                    }
+                    code.appendLine();
+                    code.appendLine("# Model 1 - Mediated variable ~ Independent variable");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("import statsmodels.formula.api as smf");
+                    code.appendFormatLine("_model  = smf.ols('{0} ~ {1}', vp_df)", mediatedValue, independentValue);
+                    code.appendLine("_result = _model.fit()");
+                    code.appendLine("display(Markdown('### Model 1 - Mediated variable ~ Independent variable'))");
+                    code.append("print(_result.summary())");
+                    if (multiCollinearity === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 1 - Multi-collinearity statistics");
+                        code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                        code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                        code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                        code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                        code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                        code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                        code.append("display(_dfr)");
+                    }
+                    if (sobelTest === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 1 - Sobel test");
+                        code.appendFormatLine("_sobel_M1   = _result.params[{0}]", independent);
+                        code.appendFormat("_sobel_M1se = _result.bse[{0}]", independent);
+                    }
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# Model 2 - Dependent variable ~ Independent variable");
+                    code.appendLine("import statsmodels.formula.api as smf");
+                    code.appendFormatLine("_model  = smf.ols('{0} ~ {1}', vp_df)", dependentValue, independentValue);
+                    code.appendLine("_result = _model.fit()");
+                    code.appendLine("display(Markdown('### Model 2 - Dependent variable ~ Independent variable'))");
+                    code.append("print(_result.summary())");
+                    if (multiCollinearity === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 2 - Multi-collinearity statistics");
+                        code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                        code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                        code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                        code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                        code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                        code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                        code.append("display(_dfr)");
+                    }
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# Model 3 - Dependent variable ~ Independent variable + Mediated variable");
+                    code.appendLine("import statsmodels.formula.api as smf");
+                    code.appendFormatLine("_model  = smf.ols('{0} ~ {1} + {2}', vp_df)", dependentValue, independentValue, mediatedValue);
+                    code.appendLine("_result = _model.fit()");
+                    code.appendLine("display(Markdown('### Model 3 - Dependent variable ~ Independent variable + Mediated variable'))");
+                    code.append("print(_result.summary())");
+                    if (multiCollinearity === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 3 - Multi-collinearity statistics");
+                        code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                        code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                        code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                        code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                        code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                        code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                        code.append("display(_dfr)");
+                    }
+                    if (sobelTest === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Model 3 - Sobel test");
+                        code.appendFormatLine("_sobel_M3   = _result.params[{0}]", mediated);
+                        code.appendFormatLine("_sobel_M3se = _result.bse[{0}]", mediated);
+                        code.appendLine();
+                        code.appendLine("# Mediated linear regression: Sobel test");
+                        code.appendLine("from scipy import stats");
+                        code.appendLine("_res = vp_sobel(_sobel_M1, _sobel_M3, _sobel_M1se, _sobel_M3se)");
+                        code.appendLine("display(Markdown('### Sobel test'))");
+                        code.append("display(pd.DataFrame(data={'Sobel Z-score':_res[0],'p-value':_res[2]},index=['Sobel test']))");
+                    }
+                    // set last model number
+                    lastModelNum = 3;
+                    break;
+                case 'dummy':
+                    // 6. Dummy variable
+                    code.appendLine();
+                    code.appendLine("# Dummy variable linear regression");
+                    code.appendLine("import statsmodels.formula.api as smf");
+                    code.appendFormatLine("_model  = smf.ols('{0} ~ {1}', vp_df)"
+                        , dependentValue, independentMulti.map(item => {
+                            let checked = $(that.wrapSelector('.vp-categorical-box input[data-name="' + item.name + '"]')).prop('checked');
+                            if (checked === true) {
+                                return 'C(' + item.name + ')';
+                            } else {
+                                return item.name;
+                            }
+                        }).join(' + '));
+                    code.appendLine("_result = _model.fit()");
+                    code.append("print(_result.summary())");
+                    if (multiCollinearity === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("# Multi-collinearity statistics");
+                        code.appendLine("from IPython.display import display");
+                        code.appendLine("from statsmodels.stats.outliers_influence import variance_inflation_factor");
+                        code.appendLine("_dfr = pd.DataFrame(_result.summary().tables[1].data[1:],columns=_result.summary().tables[1].data[0]).set_index('')");
+                        code.appendLine("for i, col in enumerate(_model.exog_names[1:]):");
+                        code.appendLine("    _vif = variance_inflation_factor(_model.exog, i+1)");
+                        code.appendLine("    _dfr.loc[col,'Tolerance'] = 1/_vif");
+                        code.appendLine("    _dfr.loc[col,'VIF'] = _vif");
+                        code.append("display(_dfr)");
+                    }
+                    break;
+            }
+
+            // Residual option
+            if (statistics === true || normTest === true || histogram === true || scatterplot === true) {
+                let residualTitle = 'Residual'
+                if (lastModelNum > 0) {
+                    residualTitle += ' - Model ' + lastModelNum;
+                }
+                code.appendLine();
+                code.appendLine();
+                code.appendFormatLine("# {0}", residualTitle);
+                code.appendLine("from IPython.display import display, Markdown");
+                code.appendLine("from scipy import stats");
+                code.appendLine("import statsmodels.api as sm");
+                if (testType === 'multiple') {
+                    if (['stepwise', 'backward', 'forward'].includes(method)) {
+                        code.appendLine("_predict =  _result.predict(sm.add_constant(vp_df[_model.exog_names[1:]]))");
+                    }
+                } else {
+                    code.appendLine("_predict  = _result.predict(vp_df)");
+                }
+                
+                code.appendLine("_residual = _result.resid");
+                code.appendLine("vp_residual = pd.DataFrame({'predict':_predict,'residual':_residual,");
+                code.appendLine("                            'predict_z':stats.zscore(_predict),'residual_z':stats.zscore(_residual)})");
+                code.appendFormatLine("display(Markdown('### {0}'))", residualTitle);
+                code.append("display(vp_residual)");
+           
+                if (statistics === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# Residual statistics");
+                    code.appendLine("display(Markdown('### Residual statistics'))");
+                    code.appendLine("display(pd.DataFrame(data={'Min':vp_residual.min(),'Max':vp_residual.max(),'Mean':vp_residual.mean(),");
+                    code.append("                           'Std. Deviation':vp_residual.std(),'N':vp_residual.count()}))");
+                }
+                if (normTest === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("# Resisual Normality test (Shapiro-Wilk)");
+                    code.appendLine("_res = stats.shapiro(vp_residual['residual_z'])");
+                    code.appendLine("display(Markdown('### Residual Normality test (Shapiro-Wilk)'))");
+                    code.append("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue},index=['Resisual Normality test (Shapiro-Wilk)']))");
+                }
+                if (histogram === true || scatterplot === true) {
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("import seaborn as sns");
+                    code.appendLine("import warnings");
+                    code.append("with warnings.catch_warnings():");
+                    let displayNum = 1;
+                    if (histogram === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("    # Residual histogram");
+                        code.appendFormatLine("    plt.subplot(2,2,{0})", displayNum++);
+                        code.appendLine("    warnings.simplefilter(action='ignore', category=Warning)");
+                        code.appendLine("    sns.histplot(data=vp_residual, x='residual_z', kde=True)");
+                        code.appendLine("    plt.title(f'Dependent variable: {_model.endog_names}')");
+                        code.append("    plt.xlabel('Regression Standardized residual')");
+                    }
+                    if (scatterplot === true) {
+                        code.appendLine();
+                        code.appendLine();
+                        code.appendLine("    # Residual scatterplot");
+                        code.appendFormatLine("    plt.subplot(2,2,{0})", displayNum++);
+                        code.appendLine("    sns.scatterplot(data=vp_residual, x='predict_z', y='residual_z')");
+                        code.appendLine("    plt.title(f'Dependent variable: {_model.endog_names}')");
+                        code.appendLine("    plt.xlabel('Regression Standardized predicted value')");
+                        code.append("    plt.ylabel('Regression Standardized residual')");
+                    }
+                    code.appendLine();
+                    code.appendLine();
+                    code.appendLine("    plt.tight_layout()");
+                    code.append("    plt.show()");
+                }
+            }
+
+            codeList.push(code.toString());
+            return codeList;
+        }
+
+    }
+
+    return Regression;
+});
\ No newline at end of file
diff --git a/visualpython/js/m_stats/ReliabAnalysis.js b/visualpython/js/m_stats/ReliabAnalysis.js
new file mode 100644
index 00000000..989931b0
--- /dev/null
+++ b/visualpython/js/m_stats/ReliabAnalysis.js
@@ -0,0 +1,158 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : ReliabAnalysis.js
+ *    Author          : Black Logic
+ *    Note            : Reliability Analysis
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 05. 24
+ *    Change Date     :
+ */
+
+//============================================================================
+// [CLASS] ReliabAnalysis
+//============================================================================
+define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/reliabAnalysis.html'),
+    'vp_base/js/com/com_util',
+    'vp_base/js/com/com_Const',
+    'vp_base/js/com/com_String',
+    'vp_base/js/com/component/PopupComponent',
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/com/component/MultiSelector',
+    'vp_base/js/m_apps/Subset'
+], function(eqHTML, com_util, com_Const, com_String, PopupComponent, DataSelector, MultiSelector, Subset) {
+
+    /**
+     * ReliabAnalysis
+     */
+    class ReliabAnalysis extends PopupComponent {
+        _init() {
+            super._init();
+            /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd', 'np', 'vp_cronbach_alpha'];
+
+            this.state = {
+                data: '',
+                variable: [],
+                ...this.state
+            };
+
+            this.subsetEditor = null;
+            this.columnSelector = null;
+        }
+
+        _bindEvent() {
+            super._bindEvent();
+            /** Implement binding events */
+            var that = this;
+
+            $(this.wrapSelector('#data')).on('change', function() {
+                let data = $(this).val();
+                that.handleVariableChange(data);
+            });
+        }
+
+        handleVariableChange(data) {
+            this.state.data = data;
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#variable'),
+                { mode: 'columns', parent: data, showDescription: false }
+        );
+        }
+
+        templateForBody() {
+            let page = $(eqHTML);
+            let that = this;
+
+            // generate dataselector
+            let dataSelector = new DataSelector({
+                pageThis: this, id: 'data', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data = data;
+                    $(that.wrapSelector('#data')).trigger('change');
+                }
+            });
+            $(page).find('#data').replaceWith(dataSelector.toTagString());
+
+            return page;
+        }
+
+        render() {
+            super.render();
+            let that = this;
+
+            // render Subset
+            this.subsetEditor = new Subset({ 
+                pandasObject: '',
+                config: { name: 'Subset', category: this.name } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data'),
+                    pageThis: this,
+                    finish: function(code) {
+                        $(that.wrapSelector('#data')).val(code);
+                        that.handleVariableChange(code);
+                    }
+                });
+
+            // render variable selector
+            this.columnSelector = new MultiSelector(this.wrapSelector('#variable'),
+                        { mode: 'columns', parent: this.state.data, selectedList: this.state.variable.map(x=>x.code), showDescription: false }
+                    );
+        }
+
+        generateCode() {
+            let { data, variable } = this.state;
+            let codeList = [];
+            let code = new com_String();
+            let that = this;
+
+            // data declaration
+            code.appendFormat("vp_df = {0}", data);
+            if (this.columnSelector) {
+                let columns = this.columnSelector.getDataList();
+                this.state.variable = columns;
+                if (columns.length > 0) {
+                    code.appendFormat("[[{0}]]", columns.map(x => x.code).join(', '));
+                }
+            }
+            code.appendLine('.dropna().copy()');
+
+            // Inner function : vp_cronbach_alpha
+
+            // Cronbach alpha
+            code.appendLine("");
+            code.appendLine("# Cronbach alpha");
+            code.appendLine("from IPython.display import display, Markdown");
+            code.appendLine("display(Markdown('### Cronbach alpha'))");
+            code.appendLine("display(pd.DataFrame({'Cronbach alpha':vp_cronbach_alpha(vp_df), 'N':vp_df.shape[1]},index=['Reliability statistics']).round(3))");
+            code.appendLine("");
+
+            // Item-total Statistics
+            code.appendLine("# Item-Total Statistics");
+            code.appendLine("_dfr = pd.DataFrame()");
+            code.appendLine("for i, col in enumerate(vp_df.columns):");
+            code.appendLine("    _sr = vp_df.drop(col,axis=1).sum(axis=1)");
+            code.appendLine("    _df_t = pd.DataFrame(data={'Scale Mean if Item Deleted':_sr.mean(),'Scale Variance if Item Deleted':_sr.var(),");
+            code.appendLine("                               'Corrected Item-Total Correlation':_sr.corr(vp_df[col]),");
+            code.appendLine("                               'Cronbach Alpha if Item Deleted':vp_cronbach_alpha(vp_df.drop(col,axis=1))}, index=[col])");
+            code.appendLine("    _dfr = pd.concat([_dfr, _df_t])");
+            code.appendLine("display(Markdown('### Item-Total Statistics'))");
+            code.append("display(_dfr.round(3))");
+            codeList.push(code.toString());
+
+            return codeList;
+        }
+
+    }
+
+    return ReliabAnalysis;
+});
\ No newline at end of file
diff --git a/visualpython/js/m_stats/StudentstTest.js b/visualpython/js/m_stats/StudentstTest.js
index da5cdcbc..15c3c702 100644
--- a/visualpython/js/m_stats/StudentstTest.js
+++ b/visualpython/js/m_stats/StudentstTest.js
@@ -13,50 +13,357 @@
 // [CLASS] StudentstTest
 //============================================================================
 define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/studentstTest.html'),
     'vp_base/js/com/com_util',
     'vp_base/js/com/com_Const',
     'vp_base/js/com/com_String',
+    'vp_base/js/com/com_generatorV2',
     'vp_base/js/com/component/PopupComponent',
-    'vp_base/js/com/component/DataSelector'
-], function(com_util, com_Const, com_String, PopupComponent, DataSelector) {
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/m_apps/Subset'
+], function(stHTML, com_util, com_Const, com_String, com_generator, PopupComponent, DataSelector, Subset) {
 
     /**
      * StudentstTest
+     * - confidence_interval is available on upper 1.10.0 version of scipy
      */
     class StudentstTest extends PopupComponent {
         _init() {
             super._init();
             /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd'];
+
+            this.state = {
+                testType: 'one-sample',
+                inputType: 'long-data',
+                data: '',
+                dataType: '',
+                testVariable: '',
+                testVariable1: '',
+                testVariable2: '',
+                groupingVariable: '',
+                group1: '',
+                group2: '',
+                pairedVariable1: '',
+                pairedVariable2: '',
+                testValue: '',
+                alterHypo: 'two-sided',
+                confInt: '95',
+                ...this.state
+            };
+
+            this.columnBindDict = {
+                'one-sample': ['testVariable'], 
+                'two-sample': ['testVariable', 'testVariable1', 'testVariable2', 'groupingVariable'],
+                'paired-sample': ['pairedVariable1', 'pairedVariable2']
+            };
+
+            this.subsetEditor = null;
+        }
+
+        _unbindEvent() {
+            super._unbindEvent();
+            var that = this;
+
+            $(document).off(this.wrapSelector('#testVariable'), 'change');
+            $(document).off(this.wrapSelector('#groupingVariable'), 'change');
         }
 
         _bindEvent() {
             super._bindEvent();
             /** Implement binding events */
             var that = this;
+
+            // change test type
+            $(this.wrapSelector('#testType')).on('change', function() {
+                let testType = $(this).val();
+                that.state.testType = testType;
+
+                that.handleVariableChange(that.state.data);
+
+                $(that.wrapSelector('.vp-st-option')).hide();
+                $(that.wrapSelector('.vp-st-option.' + testType)).show();
+                if (testType === 'two-sample') {
+                    $(that.wrapSelector('.vp-st-option.two-sample-' + that.state.inputType)).show();   
+                }
+            });
+
+            // change input type
+            $(this.wrapSelector('input[name="inputType"]:radio')).on('change', function() {
+                let inputType = $(this).val();
+                that.state.inputType = inputType;
+                $(that.wrapSelector('.vp-st-option.two-sample-long-data')).hide();
+                $(that.wrapSelector('.vp-st-option.two-sample-wide-data')).hide();
+                $(that.wrapSelector('.vp-st-option.two-sample-' + inputType)).show();
+            });
+
+            // data change event
+            $(this.wrapSelector('#data')).on('change', function() {
+                let data = $(this).val();
+                that.handleVariableChange(data);
+            });
+
+            // change test variable
+            $(document).on('change', this.wrapSelector('#testVariable'), function() {
+                if (that.state.testType === 'one-sample') {
+                    // get mean of data and show on placeholder
+                    $(that.wrapSelector('#testValue')).prop('placeholder', '');
+                    vpKernel.execute(com_util.formatString("int({0}[{1}].mean())", that.state.data, that.state.testVariable)).then(function(resultObj) {
+                        let { result } = resultObj;
+                        $(that.wrapSelector('#testValue')).prop('placeholder', result);
+                    });
+                }
+            });
+
+            // change grouping variable
+            $(document).on('change', this.wrapSelector('#groupingVariable'), function() {
+                let colCode = $(this).val();
+                var colName = $(this).find('option:selected').text();
+                var colDtype = $(this).find('option:selected').attr('data-type');
+                that.state.groupingVariable = colCode;
+                $(that.wrapSelector('#group1')).html('');
+                $(that.wrapSelector('#group2')).html('');
+                // get result and load column list
+                vpKernel.getColumnCategory(that.state.data, colCode).then(function(resultObj) {
+                    let { result } = resultObj;
+                    try {
+                        var category = JSON.parse(result);
+                        if (category && category.length > 0 && colDtype == 'object') {
+                            // if it's categorical column and its dtype is object, check 'Text' as default
+                            category.forEach(obj => {
+                                let selected1 = obj.value === that.state.group1;
+                                let selected2 = obj.value === that.state.group1;
+                                $(that.wrapSelector('#group1')).append(`<option value="${obj.value}" ${selected1?'selected':''}>${obj.label}</option>`);
+                                $(that.wrapSelector('#group2')).append(`<option value="${obj.value}" ${selected2?'selected':''}>${obj.label}</option>`);
+                            });
+                            that.state.group1 = category[0].value;
+                            that.state.group2 = category[0].value;
+                        }
+                    } catch {
+                        $(that.wrapSelector('#group1')).html('');
+                        $(that.wrapSelector('#group2')).html('');
+                    }
+                });
+            });
         }
 
-        templateForBody() {
-            /** Implement generating template */
-            return `This is sample.
-            <input type="text" id="sample" class="vp-state vp-input" readonly="" />`;
+        handleVariableChange(data) {
+            let that = this;
+            this.state.data = data;
+            let columnBindList = this.columnBindDict[this.state.testType];
+            if (this.state.dataType === 'DataFrame') {
+                // DataFrame
+                columnBindList.forEach(col => {
+                    $(that.wrapSelector('#' + col)).prop('disabled', false);
+                });
+                com_generator.vp_bindColumnSource(that, 'data', columnBindList, 'select', false, false);
+            } else {
+                // Series
+                columnBindList.forEach(col => {
+                    $(that.wrapSelector('#' + col)).html('');
+                    $(that.wrapSelector('#' + col)).prop('disabled', true);
+                });
+            }
         }
 
-        render() {
-            super.render();
+        templateForBody() {
+            let page = $(stHTML);
+            let that = this;
 
+            // generate dataselector
             let dataSelector = new DataSelector({
-                type: 'data',
-                pageThis: this,
-                id: 'sample',
-                finish: function() {
-                    ;
+                pageThis: this, id: 'data', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data = data;
+                    that.state.dataType = type;
+                    $(that.wrapSelector('#data')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data = data;
+                    that.state.dataType = type;
+                    $(that.wrapSelector('#data')).trigger('change');
                 }
             });
-            $(this.wrapSelector('#sample')).replaceWith(dataSelector.toTagString());
+            $(page).find('#data').replaceWith(dataSelector.toTagString());
+
+            return page;
+        }
+
+        render() {
+            super.render();
+            let that = this;
+
+            // render Subset
+            this.subsetEditor = new Subset({ 
+                pandasObject: '',
+                config: { name: 'Subset' } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data'),
+                    pageThis: this,
+                    finish: function(code, state) {
+                        that.state.data = code;
+                        that.state.dataType = state.returnType;
+                        $(that.wrapSelector('#data')).val(code);
+                        $(that.wrapSelector('#data')).trigger('change');
+                    }
+                });
+
+            if (this.state.data !== '') {
+                let columnBindList = this.columnBindDict[this.state.testType];
+                com_generator.vp_bindColumnSource(this, 'data', columnBindList, 'select', false, false);
+            }
+
+            // control display option
+            $(this.wrapSelector('.vp-st-option')).hide();
+            $(this.wrapSelector('.vp-st-option.' + this.state.testType)).show();
+            if (this.state.testType === 'two-sample') {
+                $(this.wrapSelector('.vp-st-option.two-sample-' + this.state.inputType)).show();   
+            }
         }
 
         generateCode() {
-            return "print('sample code')";
+            let { 
+                testType, inputType, data, 
+                testVariable, testVariable1, testVariable2, groupingVariable,
+                pairedVariable1, pairedVariable2,
+                group1, group2,
+                testValue, alterHypo, confInt 
+            } = this.state;
+            let codeList = [];
+            let code = new com_String();
+
+            // 95% -> 0.95
+            confInt = confInt/100;
+
+            switch (testType) {
+                case 'one-sample':
+                    code.appendLine("# One-sample t-test");
+                    // variable declaration
+                    code.appendFormatLine("vp_df = {0}.dropna().copy()", data);
+                    code.appendLine("");
+                    // 1. Normality test
+                    code.appendLine("# Normality test (Shapiro-Wilk)");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("from scipy import stats");
+                    code.appendFormatLine("_res = stats.shapiro(vp_df[{0}])", testVariable);
+                    code.appendLine("display(Markdown('### Normality test (Shapiro-Wilk)'))");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue},index=['Normality test (Shapiro-Wilk)']))");
+                    code.appendLine("");
+                    // 2. One-sample Statistics
+                    code.appendLine("# Statistics");
+                    code.appendLine("display(Markdown('### Statistics'))");
+                    code.appendFormatLine("display(pd.DataFrame(data={'N':vp_df[{0}].size,'Mean':vp_df[{1}].mean(),", testVariable, testVariable);
+                    code.appendFormatLine("                           'Std. Deviation':vp_df[{0}].std(),", testVariable);
+                    code.appendFormatLine("                           'Std. Error Mean':vp_df[{0}].std()/np.sqrt(vp_df[{1}].size)},", testVariable, testVariable);
+                    code.appendLine("                     index=['Statistics']))");
+                    code.appendLine("");
+                    // 3. One-sample t-test
+                    code.appendLine("# One-sample t-test");
+                    code.appendFormatLine("_res = stats.ttest_1samp(vp_df[{0}], popmean={1}, alternative='{2}')", testVariable, testValue, alterHypo);
+                    code.appendFormatLine("_lower, _upper = _res.confidence_interval(confidence_level={0})", confInt);
+                    code.appendLine("display(Markdown('### One-sample t-test'))");
+                    code.appendFormatLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'dof':_res.df,'Alternative':'{0}',", alterHypo);
+                    code.appendFormatLine("                           'p-value':_res.pvalue,'Test Value':{0},'Mean difference':vp_df[{1}].mean()-{2},", testValue, testVariable, testValue);
+                    code.appendFormatLine("                           'Confidence interval':{0},'Lower':_lower,'Upper':_upper},", confInt);
+                    code.append("                     index=['One-sample t-test']))");
+                    break;
+                case 'two-sample':
+                    code.appendLine("# Independent two-sample t-test");
+                    // variable declaration
+                    if (inputType === 'long-data') {
+                        code.appendFormatLine("vp_df1 = df[(df[{0}] == '{1}')][{2}].dropna().copy()", groupingVariable, group1, testVariable);
+                        code.appendFormatLine("vp_df2 = df[(df[{0}] == '{1}')][{2}].dropna().copy()", groupingVariable, group2, testVariable);
+                    } else if (inputType === 'wide-data') {
+                        code.appendFormatLine("vp_df1 = df[{0}].dropna().copy()", testVariable1);
+                        code.appendFormatLine("vp_df2 = df[{0}].dropna().copy()", testVariable2);
+                    }
+                    code.appendLine("");
+                    // 1. Normality test
+                    code.appendLine("# Normality test (Shapiro-Wilk)");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("from scipy import stats");
+                    code.appendLine("_res1 = stats.shapiro(vp_df1)");
+                    code.appendLine("_res2 = stats.shapiro(vp_df2)");
+                    code.appendLine("display(Markdown('### Normality test (Shapiro-Wilk)'))");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':[_res1.statistic,_res2.statistic],'p-value':[_res1.pvalue,_res2.pvalue]},");
+                    code.appendLine("                    index=[['Normality test (Shapiro-Wilk)' for i in range(2)],['Variable1','Variable2']]))");
+                    code.appendLine("");
+                    // 2. Equal Variance test
+                    code.appendLine("# Equal Variance test (Levene)");
+                    code.appendLine("display(Markdown('### Equal Variance test (Levene)'))");
+                    code.appendLine("_res = stats.levene(vp_df1, vp_df2, center='mean')");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue}, index=['Equal Variance test (Levene)']))");
+                    code.appendLine("");
+                    // 3. Independent two-sample Statistics
+                    code.appendLine("# Statistics");
+                    code.appendLine("display(Markdown('### Statistics'))");
+                    code.appendLine("display(pd.DataFrame(data={'N':[vp_df1.size,vp_df2.size],");
+                    code.appendLine("                           'Mean':[vp_df1.mean(),vp_df2.mean()],");
+                    code.appendLine("                           'Std. Deviation':[vp_df1.std(),vp_df2.std()],");
+                    code.appendLine("                           'Std. Error mean':[vp_df1.std()/np.sqrt(vp_df1.size),");
+                    code.appendLine("                                              vp_df2.std()/np.sqrt(vp_df2.size )]},");
+                    code.appendLine("                     index=[['Statistics' for i in range(2)],['Variable1','Variable2']]))");
+                    code.appendLine("");
+                    // 4. Independent two-sample t-test
+                    code.appendLine("# Independent two-sample t-test");
+                    code.appendFormatLine("_res1 = stats.ttest_ind(vp_df1, vp_df2, equal_var=True,  alternative='{0}')", alterHypo);
+                    code.appendFormatLine("_res2 = stats.ttest_ind(vp_df1, vp_df2, equal_var=False, alternative='{0}')", alterHypo);
+                    code.appendLine("display(Markdown('### Independent two-sample t-test'))");
+                    code.appendFormatLine("display(pd.DataFrame(data={'Statistic':[_res1.statistic,_res2.statistic],'Alternative':['{0}' for i in range(2)],", alterHypo);
+                    code.appendLine("                           'p-value':[_res1.pvalue,_res2.pvalue],");
+                    code.appendLine("                           'Mean difference':[vp_df1.mean()-vp_df2.mean() for i in range(2)]},");
+                    code.appendLine("                     index=[['Independent two-sample t-test' for i in range(2)],['Equal variance' for i in range(2)],[True,False]]))");
+                    code.append("display(Markdown('If equal_var is False, perform Welch\\\'s t-test, which does not assume equal population variance'))");
+                    break;
+                case 'paired-sample':
+                    // variable declaration
+                    code.appendLine("# Paired samples t-test");
+                    code.appendFormatLine("vp_df = {0}.dropna().copy()", data);
+                    code.appendLine("");
+                    code.appendFormatLine("try: vp_df[{0}].reset_index(drop=True, inplace=True)", pairedVariable1);
+                    code.appendLine("except: pass");
+                    code.appendFormatLine("try: vp_df[{0}].reset_index(drop=True, inplace=True)", pairedVariable2);
+                    code.appendLine("except: pass");
+                    code.appendLine("");
+                    // 1. Normality test
+                    code.appendLine("# Normality test (Shapiro-Wilk)");
+                    code.appendLine("from IPython.display import display, Markdown");
+                    code.appendLine("from scipy import stats");
+                    code.appendFormatLine("_res = stats.shapiro(vp_df[{0}]-vp_df[{1}])", pairedVariable1, pairedVariable2);
+                    code.appendLine("display(Markdown('### Normality test (Shapiro-Wilk)'))");
+                    code.appendLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'p-value':_res.pvalue},");
+                    code.appendLine("                     index=['Normality test (Shapiro-Wilk): Paired differences']))");
+                    code.appendLine("");
+                    // 2. Paired samples Statistics
+                    code.appendLine("# Statistics");
+                    code.appendLine("display(Markdown('### Statistics'))");
+                    code.appendFormatLine("display(pd.DataFrame(data={'N':[vp_df[{0}].size,vp_df[{1}].size,vp_df[{2}].size],", pairedVariable1, pairedVariable2, pairedVariable1);
+                    code.appendFormatLine("                   'Mean':[vp_df[{0}].mean(),vp_df[{1}].mean(),(vp_df[{2}]-vp_df[{3}]).mean()],", pairedVariable1, pairedVariable2, pairedVariable1, pairedVariable2);
+                    code.appendFormatLine("                   'Std. Deviation':[vp_df[{0}].std(),vp_df[{1}].std(),(vp_df[{2}]-vp_df[{3}]).std()],", pairedVariable1, pairedVariable2, pairedVariable1, pairedVariable2);
+                    code.appendFormatLine("                   'Std. Error mean':[vp_df[{0}].std()/np.sqrt(vp_df[{1}].size),", pairedVariable1, pairedVariable1);
+                    code.appendFormatLine("                                      vp_df[{0}].std()/np.sqrt(vp_df[{1}].size),", pairedVariable2, pairedVariable2);
+                    code.appendFormatLine("                                      (vp_df[{0}]-vp_df[{1}]).std()/np.sqrt(vp_df[{2}].size)]},", pairedVariable1, pairedVariable2, pairedVariable1);
+                    code.appendLine("             index=[['Statistics' for i in range(3)],['Variable1','Variable2','Paired differences']]))");
+                    code.appendLine("");
+                    // 3. Paired samples t-test
+                    code.appendLine("# Paired samples t-test");
+                    code.appendFormatLine("_res = stats.ttest_rel(vp_df[{0}], vp_df[{1}], alternative='{2}')", pairedVariable1, pairedVariable2, alterHypo);
+                    code.appendFormatLine("_lower, _upper = _res.confidence_interval(confidence_level={0})", confInt);
+                    code.appendLine("display(Markdown('### Paired samples t-test'))");
+                    code.appendFormatLine("display(pd.DataFrame(data={'Statistic':_res.statistic,'dof':_res.df,'Alternative':'{0}',", alterHypo);
+                    code.appendFormatLine("                           'p-value':_res.pvalue,'Mean difference':(vp_df[{0}]-vp_df[{1}]).mean(),", pairedVariable1, pairedVariable2);
+                    code.appendFormatLine("                           'Confidence interval':{0},'Lower':_lower,'Upper':_upper},", confInt);
+                    code.append("                     index=['Paired samples t-test']))");
+                    break;
+            }
+            codeList.push(code.toString());
+            
+
+            return codeList;
         }
 
     }
diff --git a/visualpython/js/menu/MenuItem.js b/visualpython/js/menu/MenuItem.js
index 22343b2e..8cebdd73 100644
--- a/visualpython/js/menu/MenuItem.js
+++ b/visualpython/js/menu/MenuItem.js
@@ -148,6 +148,10 @@ define([
                 // LAB: img to url
                 // page.appendFormatLine('<img src="{0}">', com_Const.IMAGE_PATH + apps.icon);
                 page.appendFormatLine('<div class="apps-icon {0}"></div>', id);
+                // Exception for title alignment
+                if (id === 'stats_studentstTest') {
+                    name = "Student's<br/>t-test";
+                }
                 page.appendFormatLine('<div class="vp-menuitem-apps-name">{0}</div>', name);
                 page.append('</div>');
             } else {
diff --git a/visualpython/python/userCommand.py b/visualpython/python/userCommand.py
index 6383f560..586eea36 100644
--- a/visualpython/python/userCommand.py
+++ b/visualpython/python/userCommand.py
@@ -7,6 +7,8 @@
 import pandas as _vp_pd
 import numpy as _vp_np
 import matplotlib.pyplot as _vp_plt
+import scipy.stats as _vp_stats
+import statsmodels.api as _vp_sm
 import fitz
 import nltk
 nltk.download('punkt')
@@ -46,23 +48,48 @@ def vp_pdf_get_sentence(fname_lst):
 ######
 # Visual Python: Data Analysis > Frame
 ######
-def vp_drop_outlier(df, col, weight=1.5):
-    sr = df[col]
-    
-    q25 = _vp_np.percentile(sr.values, 25)
-    q75 = _vp_np.percentile(sr.values, 75)
-    
-    iqr   = q75 - q25
-    iqr_w = iqr * weight
-    
-    val_l = q25 - iqr_w
-    val_h = q75 + iqr_w
-    
-    outlier_index = sr[(sr < val_l) | (sr > val_h)].index
-    
-    df_res = df.drop(outlier_index).copy()
-    
-    return df_res
+def vp_fill_outlier(df, col_lst, fill_type='iqr', fill_value_lst=[], weight=1.5):
+    dfr = df.copy()
+    for idx, col in enumerate(col_lst):
+        sr = dfr[col]
+        q25 = _vp_np.percentile(sr.values, 25)
+        q75 = _vp_np.percentile(sr.values, 75)
+        iqr   = q75 - q25
+        iqr_w = iqr * weight
+        val_l = q25 - iqr_w
+        val_h = q75 + iqr_w
+        if fill_type == 'mean':
+            f_val = sr[~((sr < val_l) | (sr > val_h))].mean()
+        elif fill_type == 'median':
+            f_val = sr[~((sr < val_l) | (sr > val_h))].median()
+        elif fill_type == 'value':
+            f_val = fill_value_lst[idx]
+        elif fill_type == 'NA':
+            f_val = _vp_np.nan
+        if fill_type == 'iqr':
+            dfr.loc[(sr < val_l), col] = val_l
+            dfr.loc[(sr > val_h), col] = val_h
+        else:
+            dfr.loc[(sr < val_l) | (sr > val_h), col] = f_val
+    return dfr
+######
+# Visual Python: Data Analysis > Frame
+######
+def vp_drop_outlier(df, col_lst, weight=1.5):
+    dfr = df.copy()
+    outlier_index_lst = []
+    for idx, col in enumerate(col_lst):
+        sr = dfr[col]
+        q25 = _vp_np.percentile(sr.values, 25)
+        q75 = _vp_np.percentile(sr.values, 75)
+        iqr   = q75 - q25
+        iqr_w = iqr * weight
+        val_l = q25 - iqr_w
+        val_h = q75 + iqr_w
+        outlier_index_lst += sr[(sr < val_l) | (sr > val_h)].index.to_list()
+    outlier_index_lst = list(set(outlier_index_lst))
+    dfr.drop(outlier_index_lst, inplace=True)
+    return dfr
 ######
 # Visual Python: Machine Learning > Model Info
 ######
@@ -134,4 +161,121 @@ def _single(ax):
         for idx, ax in _vp_np.ndenumerate(axs):
             _single(ax)
     else:
-        _single(axs)
\ No newline at end of file
+        _single(axs)
+######
+# Visual Python: Statistics > Correlation Analysis
+######
+def vp_confidence_interval_corr(x, y, method='pearson', alpha=0.05):
+    try: x=_vp_pd.Series(x); y=_vp_pd.Series(y)
+    except: return _vp_np.nan
+
+    corr_func = {'pearson':_vp_stats.pearsonr,'spearman':_vp_stats.spearmanr,'kendall':_vp_stats.kendalltau}
+    se_diff   = {'pearson':3,'spearman':3,'kendall':4}
+    se_func   = {'pearson': lambda corr: 1,
+                 'spearman':lambda corr: 1 + corr ** 2 / 2.,
+                 'kendall': lambda corr: .437 }
+                     
+    corr, pvalue = corr_func[method](x,y)
+    
+    z  = _vp_np.log((1 + corr) / (1 - corr)) / 2
+    se = _vp_np.sqrt(se_func[method](corr) / (x.size - se_diff[method]))
+    
+    z_lower = z - _vp_stats.norm.ppf(1 - alpha / 2.) * se
+    z_upper = z + _vp_stats.norm.ppf(1 - alpha / 2.) * se
+    
+    corr_lower = (_vp_np.exp(2 * z_lower) - 1) / (_vp_np.exp(2 * z_lower) + 1)
+    corr_upper = (_vp_np.exp(2 * z_upper) - 1) / (_vp_np.exp(2 * z_upper) + 1)    
+    
+    return corr, pvalue, corr_lower, corr_upper
+######
+# Visual Python: Statistics > Reliability Analysis
+######
+def vp_cronbach_alpha(data):
+    _corr = data.corr()
+    _N = data.shape[1]
+    _rs = _vp_np.array([])
+    for i, col in enumerate(_corr.columns):
+        _sum = _corr[col][i+1:].values
+        _rs  = _vp_np.append(_sum, _rs)
+    _mean = _vp_np.mean(_rs)
+    
+    return (_N*_mean)/(1+(_N-1)*_mean)
+######
+# Visual Python: Statistics > ANOVA
+######
+def vp_confidence_interval(var, confidence_level=0.95):
+    try: sr = _vp_pd.Series(var)
+    except: return _vp_np.nan
+    return _vp_stats.t.interval(confidence_level, df=sr.count()-1, loc=sr.mean(), scale=sr.std() / _vp_np.sqrt(sr.count()) )
+######
+# Visual Python: Statistics > ANOVA
+######
+def vp_sem(var):
+    try: sr = _vp_pd.Series(var)
+    except: return _vp_np.nan
+    return sr.std() / _vp_np.sqrt(sr.count())
+######
+# Visual Python: Statistics > Regression - Multiple linear regression > Method: Stepwise
+######
+def vp_stepwise_select(df_x, df_y, alpha=0.05):
+    select_list = list()
+    while len(df_x.columns) > 0:
+        col_list = list(set(df_x.columns)-set(select_list))
+        sr_pval = _vp_pd.Series(index=col_list)
+        for col in col_list:
+            result = _vp_sm.OLS(df_y, _vp_sm.add_constant(df_x[select_list+[col]])).fit()
+            sr_pval[col] = result.pvalues[col]
+        best_pval = sr_pval.min()
+        if best_pval < alpha:
+            select_list.append(sr_pval.idxmin())
+            while len(select_list) > 0:
+                result = _vp_sm.OLS(df_y, _vp_sm.add_constant(df_x[select_list])).fit()
+                sr_pval2 = result.pvalues.iloc[1:]
+                worst_pval = sr_pval2.max()
+                if worst_pval > alpha:
+                    select_list.remove(sr_pval2.idxmax())
+                else:
+                    break
+        else:
+            break
+    return select_list
+######
+# Visual Python: Statistics > Regression - Multiple linear regression > Method: Backward
+######
+def vp_backward_select(df_x, df_y, alpha=0.05):
+    select_list=list(df_x.columns)
+    while True:
+        result = _vp_sm.OLS(df_y, _vp_sm.add_constant(_vp_pd.DataFrame(df_x[select_list]))).fit()
+        sr_pval = result.pvalues.iloc[1:]
+        worst_pval = sr_pval.max()
+        if worst_pval > alpha:
+            select_list.remove(sr_pval.idxmax())
+        else:
+            break
+    return select_list
+######
+# Visual Python: Statistics > Regression - Multiple linear regression > Method: Forward
+######
+def vp_forward_select(df_x, df_y, alpha=0.05):
+    select_list = list()
+    while True:
+        col_list = list(set(df_x.columns)-set(select_list))
+        sr_pval = _vp_pd.Series(index=col_list)
+        for col in col_list:
+            result = _vp_sm.OLS(df_y, _vp_sm.add_constant(_vp_pd.DataFrame(df_x[select_list+[col]]))).fit()
+            sr_pval[col] = result.pvalues[col]
+        best_pval = sr_pval.min()
+        if best_pval < alpha:
+            select_list.append(sr_pval.idxmin())
+        else:
+            break
+            
+    return select_list
+######
+# Visual Python: Statistics > Regression - Mediated linear regression
+######
+def vp_sobel(a, b, sea, seb):
+    z = (a * b) / ( (a**2)*(seb**2) + (b**2)*(sea**2) )**0.5
+    one_pvalue = _vp_stats.norm.sf(abs(z))
+    two_pvalue = _vp_stats.norm.sf(abs(z))*2
+    return z, one_pvalue, two_pvalue
\ No newline at end of file


Test patch:
diff --git a/visualpython/html/m_stats/chi2test.html b/visualpython/html/m_stats/chi2test.html
new file mode 100644
index 00000000..1c1f4dda
--- /dev/null
+++ b/visualpython/html/m_stats/chi2test.html
@@ -0,0 +1,36 @@
+<!--
+    Project Name    : Visual Python
+    Description     : GUI-based Python code generator
+    File Name       : chi2test.html
+    Author          : Black Logic
+    Note            : Chi-square test of independence
+    License         : GNU GPLv3 with Visual Python special exception
+    Date            : 2023. 05. 24
+    Change Date     : 
+-->
+<body>
+    <div class="vp-grid-box">
+        <div class="vp-grid-border-box">
+            <div class="vp-grid-box vp-st-option-box">
+                <div class="vp-grid-col-110">
+                    <label for="data" class="vp-orange-text">Data</label>
+                    <div class="vp-flex-gap5">
+                        <input type="text" id="data" class="vp-state vp-input" required/>
+                    </div>
+                    <label for="row" class="vp-orange-text">Row</label>
+                    <select id="row" class="vp-select vp-state" required></select>
+                    <label for="column" class="vp-orange-text">Column</label>
+                    <select id="column" class="vp-select vp-state" required></select>
+                </div>
+            </div>
+        </div>
+        <div class="vp-grid-border-box">
+            <label for="barplot">Display</label>
+            <div class="vp-grid-box">
+                <label><input type="checkbox" id="barplot" class="vp-state" checked><span>Barplot</span></label>
+                <label><input type="checkbox" id="crossTab" class="vp-state" checked><span>Cross tabulation</span></label>
+                <label><input type="checkbox" id="cramersCoef" class="vp-state" checked><span>Cramers V coefficient</span></label>
+            </div>
+        </div>
+    </div>
+</body>
\ No newline at end of file
diff --git a/visualpython/img/apps/apps_chi2test.svg b/visualpython/img/apps/apps_chi2test.svg
new file mode 100644
index 00000000..605e5169
--- /dev/null
+++ b/visualpython/img/apps/apps_chi2test.svg
@@ -0,0 +1,5 @@
+<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M20.0074 13.1027C20.0074 13.2384 19.9235 13.3926 19.7556 13.5654C19.5877 13.7381 19.414 13.8276 19.2346 13.8337H19.1391C18.6644 13.8337 18.3373 13.6888 18.1578 13.3988C17.9899 13.1335 17.7873 12.5691 17.55 11.7054L17.4718 11.4C17.466 11.4062 17.3618 11.5296 17.1592 11.7702C16.9566 12.0108 16.7308 12.273 16.4819 12.5567C16.2329 12.8405 16.0072 13.1027 15.8046 13.3433C15.6019 13.5839 15.489 13.7073 15.4659 13.7134C15.4427 13.732 15.4138 13.7412 15.3791 13.7412C15.3385 13.7412 15.3038 13.7258 15.2748 13.6949C15.2459 13.6641 15.2285 13.6271 15.2227 13.5839C15.217 13.5407 15.2546 13.4698 15.3356 13.3711C15.4167 13.2724 15.7438 12.8899 16.3169 12.2236L17.3676 11.0021L17.3068 10.7338C16.8668 9.04344 16.4906 8.19827 16.1779 8.19827C15.9985 8.19827 15.8595 8.34324 15.7611 8.63319C15.7553 8.67637 15.7003 8.69797 15.5961 8.69797C15.4804 8.69797 15.4225 8.66712 15.4225 8.60543C15.4225 8.55608 15.4456 8.47896 15.4919 8.37409C15.5383 8.26921 15.6338 8.15508 15.7785 8.0317C15.9232 7.90832 16.094 7.84971 16.2908 7.85588C16.7308 7.85588 17.0492 7.98543 17.246 8.24454C17.3502 8.39876 17.4631 8.64553 17.5847 8.98483C17.7063 9.32413 17.8018 9.62641 17.8713 9.89169L17.9668 10.2803C17.9841 10.2803 18.3141 9.90094 18.9567 9.14214C19.5993 8.38334 19.9409 7.99469 19.9814 7.97618C20.0161 7.96384 20.0422 7.95767 20.0595 7.95767C20.1637 7.95767 20.2158 8.01011 20.2158 8.11499C20.2158 8.15817 20.1782 8.22603 20.1029 8.31857C20.0277 8.4111 19.7093 8.78742 19.1477 9.44751C18.4473 10.2618 18.0883 10.6813 18.071 10.706L18.1491 11.0021C18.3228 11.6931 18.5138 12.2822 18.7223 12.7696C18.9307 13.2569 19.1101 13.5006 19.2606 13.5006C19.3417 13.5006 19.4198 13.4636 19.4951 13.3896C19.5704 13.3155 19.6253 13.223 19.6601 13.112C19.6775 13.0503 19.6977 13.0163 19.7209 13.0102C19.744 13.004 19.7845 13.0009 19.8424 13.0009C19.9003 13.0009 19.9409 13.004 19.964 13.0102C19.993 13.0287 20.0074 13.0595 20.0074 13.1027Z" fill="white" stroke="white" stroke-width="0.2"/>
+<path d="M21.1258 6.50654C21.0501 6.50654 20.9898 6.47966 20.945 6.42588C20.9001 6.37211 20.8777 6.30638 20.8777 6.22871C20.8777 6.0166 20.952 5.83288 21.1006 5.67753C21.2491 5.52218 21.4342 5.44451 21.6556 5.44451C21.9107 5.44451 22.1238 5.52816 22.2948 5.69546C22.4658 5.86275 22.5527 6.07934 22.5555 6.34522C22.5555 6.47368 22.5275 6.59617 22.4714 6.71268C22.4154 6.82919 22.3481 6.93076 22.2696 7.01739C22.1911 7.10403 22.0789 7.21456 21.9332 7.349C21.8322 7.44161 21.6921 7.57903 21.5127 7.76126L21.2646 8.01221L21.5841 8.01669C22.0243 8.01669 22.2584 8.00922 22.2864 7.99428C22.306 7.98831 22.3397 7.85537 22.3873 7.59546V7.58202H22.5555V7.59546C22.5527 7.60442 22.5345 7.74035 22.5009 8.00325C22.4672 8.26614 22.4462 8.40356 22.4378 8.41551V8.42896H20.8777V8.34381V8.29004C20.8777 8.26913 20.8861 8.24672 20.9029 8.22282C20.9197 8.19892 20.9618 8.14664 21.0291 8.06598C21.1104 7.97038 21.1805 7.88674 21.2393 7.81504C21.2646 7.78516 21.3122 7.7299 21.3823 7.64924C21.4524 7.56857 21.5 7.51331 21.5253 7.48343C21.5505 7.45356 21.5912 7.40427 21.6472 7.33556C21.7033 7.26684 21.7425 7.21606 21.765 7.1832C21.7874 7.15033 21.8196 7.10552 21.8617 7.04876C21.9037 6.992 21.9332 6.9442 21.95 6.90536C21.9668 6.86653 21.9878 6.82321 22.0131 6.77541C22.0383 6.72761 22.0565 6.67981 22.0677 6.63201C22.0789 6.58422 22.0888 6.5394 22.0972 6.49758C22.1056 6.45576 22.1098 6.40646 22.1098 6.3497C22.1098 6.16149 22.0621 5.99868 21.9668 5.86126C21.8715 5.72384 21.7355 5.65512 21.5589 5.65512C21.4664 5.65512 21.3851 5.68052 21.315 5.7313C21.2449 5.78209 21.1959 5.83138 21.1678 5.87918C21.1398 5.92698 21.1258 5.95536 21.1258 5.96432C21.1258 5.96731 21.1328 5.96881 21.1468 5.96881C21.1973 5.96881 21.2491 5.98972 21.3024 6.03154C21.3557 6.07337 21.3823 6.14208 21.3823 6.23767C21.3823 6.31236 21.3599 6.3751 21.315 6.42588C21.2702 6.47667 21.2071 6.50356 21.1258 6.50654Z" fill="white"/>
+<path d="M3 24.9919H2.25C2.25 25.4061 2.58579 25.7419 3 25.7419V24.9919ZM3.75 3C3.75 2.58579 3.41421 2.25 3 2.25C2.58579 2.25 2.25 2.58579 2.25 3H3.75ZM24.0257 25.7402C24.439 25.7122 24.7512 25.3544 24.7232 24.9412C24.6951 24.5279 24.3374 24.2156 23.9241 24.2437L24.0257 25.7402ZM5.24731 21.2739L5.96352 21.4965L5.98757 21.4191L5.99453 21.3384L5.24731 21.2739ZM25 25.7419C25.4142 25.7419 25.75 25.4061 25.75 24.9919C25.75 24.5777 25.4142 24.2419 25 24.2419V25.7419ZM3.75 24.9919V3H2.25V24.9919H3.75ZM13.6139 17.2304C14.3682 18.3535 15.8676 20.5117 17.6821 22.3622C18.5893 23.2873 19.5965 24.1576 20.6499 24.7831C21.6987 25.406 22.8509 25.8199 24.0257 25.7402L23.9241 24.2437C23.1657 24.2951 22.3212 24.0311 21.4158 23.4934C20.5148 22.9584 19.6099 22.1857 18.7531 21.3119C17.0397 19.5646 15.6031 17.5018 14.8592 16.3941L13.6139 17.2304ZM3.52837 25.5242C4.29598 24.7622 4.7865 24.1819 5.14714 23.5554C5.50549 22.9329 5.71358 22.3007 5.96352 21.4965L4.5311 21.0513C4.27878 21.8631 4.11233 22.3464 3.84714 22.807C3.58424 23.2638 3.20223 23.7344 2.47163 24.4597L3.52837 25.5242ZM5.99453 21.3384C6.4582 15.9669 7.15211 12.9937 7.89252 11.536C8.2627 10.8072 8.57442 10.5969 8.7403 10.5439C8.87458 10.5009 9.08669 10.5079 9.44289 10.7597C9.80191 11.0134 10.1994 11.4457 10.6169 12.0186C11.0284 12.5832 11.4299 13.243 11.8071 13.9137C12.1842 14.5839 12.5283 15.2491 12.8315 15.8293C13.1248 16.3904 13.3982 16.9092 13.6139 17.2304L14.8592 16.3941C14.7003 16.1575 14.47 15.7259 14.1609 15.1346C13.8619 14.5624 13.5055 13.8733 13.1145 13.1783C12.7237 12.4837 12.2897 11.7671 11.8292 11.1352C11.3747 10.5115 10.8637 9.92708 10.3087 9.53477C9.7508 9.14045 9.04326 8.87205 8.28325 9.11519C7.55483 9.34822 6.99762 9.98557 6.55514 10.8567C5.67025 12.5989 4.96399 15.8352 4.50009 21.2094L5.99453 21.3384ZM3 25.7419H14.2366V24.2419H3V25.7419ZM13.4866 16.8122V24.9919H14.9866V16.8122H13.4866ZM14.2366 25.7419H25V24.2419H14.2366V25.7419Z" fill="white"/>
+</svg>
diff --git a/visualpython/js/m_stats/Chi2test.js b/visualpython/js/m_stats/Chi2test.js
new file mode 100644
index 00000000..4eaaf70a
--- /dev/null
+++ b/visualpython/js/m_stats/Chi2test.js
@@ -0,0 +1,214 @@
+/*
+ *    Project Name    : Visual Python
+ *    Description     : GUI-based Python code generator
+ *    File Name       : Chi2test.js
+ *    Author          : Black Logic
+ *    Note            : Chi-square test of independence
+ *    License         : GNU GPLv3 with Visual Python special exception
+ *    Date            : 2023. 05. 24
+ *    Change Date     :
+ */
+
+//============================================================================
+// [CLASS] Chi2test
+//============================================================================
+define([
+    __VP_TEXT_LOADER__('vp_base/html/m_stats/chi2test.html'),
+    'vp_base/js/com/com_util',
+    'vp_base/js/com/com_Const',
+    'vp_base/js/com/com_String',
+    'vp_base/js/com/com_generatorV2',
+    'vp_base/js/com/component/PopupComponent',
+    'vp_base/js/com/component/DataSelector',
+    'vp_base/js/m_apps/Subset'
+], function(nmHTML, com_util, com_Const, com_String, com_generator, PopupComponent, DataSelector, Subset) {
+
+    /**
+     * Chi2test
+     */
+    class Chi2test extends PopupComponent {
+        _init() {
+            super._init();
+            /** Write codes executed before rendering */
+            this.config.sizeLevel = 2;
+            this.config.checkModules = ['pd'];
+
+            this.state = {
+                data: '',
+                dataType: '',
+                row: '',
+                column: '',
+                barplot: true,
+                crossTab: true,
+                cramersCoef: true,
+                ...this.state
+            };
+
+            this.subsetEditor = null;
+        }
+
+        _bindEvent() {
+            super._bindEvent();
+            /** Implement binding events */
+            var that = this;
+
+            $(this.wrapSelector('#data')).on('change', function() {
+                let bindIdList = ['row', 'column'];
+                if (that.state.dataType === 'Series') {
+                    // Series
+                    bindIdList.forEach(id => {
+                        $(that.wrapSelector('#' + id)).html('');
+                        $(that.wrapSelector('#' + id)).prop('disabled', true);
+                    });
+                } else {
+                    // DataFrame
+                    bindIdList.forEach(id => {
+                        $(that.wrapSelector('#' + id)).prop('disabled', false);
+                    });
+                    com_generator.vp_bindColumnSource(that, 'data', bindIdList, 'select', false, false);
+                }
+            });
+        }
+
+        handleVariableChange(data) {
+            this.state.data = data;
+            let bindIdList = ['row', 'column'];
+            if (that.state.dataType === 'DataFrame') {
+                // DataFrame
+                bindIdList.forEach(id => {
+                    $(that.wrapSelector('#' + id)).html('');
+                    $(that.wrapSelector('#' + id)).prop('disabled', false);
+                });
+                com_generator.vp_bindColumnSource(that, 'data', bindIdList, 'select', false, false);
+            } else {
+                // Others
+                bindIdList.forEach(id => {
+                    $(that.wrapSelector('#' + id)).html('');
+                    $(that.wrapSelector('#' + id)).prop('disabled', true);
+                });
+            }
+        }
+
+        templateForBody() {
+            let page = $(nmHTML);
+            let that = this;
+
+            let dataSelector = new DataSelector({
+                pageThis: this, id: 'data', placeholder: 'Select data', required: true, boxClasses: 'vp-flex-gap5',
+                allowDataType: ['DataFrame'], withPopup: false,
+                finish: function(data, type) {
+                    that.state.data = data;
+                    that.state.dataType = type;
+                    $(that.wrapSelector('#data')).trigger('change');
+                },
+                select: function(data, type) {
+                    that.state.data = data;
+                    that.state.dataType = type;
+                    $(that.wrapSelector('#data')).trigger('change');
+                }
+            });
+            $(page).find('#data').replaceWith(dataSelector.toTagString());
+
+            return page;
+        }
+
+        render() {
+            super.render();
+            let that = this;
+
+            // render Subset
+            this.subsetEditor = new Subset({ 
+                pandasObject: '',
+                config: { name: 'Subset', category: this.name } }, 
+                {
+                    useAsModule: true,
+                    useInputColumns: true,
+                    targetSelector: this.wrapSelector('#data'),
+                    pageThis: this,
+                    finish: function(code, state) {
+                        that.state.data = code;
+                        that.state.dataType = state.returnType;
+                        $(that.wrapSelector('#data')).val(code);
+                        $(that.wrapSelector('#data')).trigger('change');
+                    }
+                });
+
+            // bind column if data exist
+            if (this.state.data !== '') {
+                com_generator.vp_bindColumnSource(this, 'data', ['row', 'column'], 'select', false, false);
+            }
+        }
+
+        generateCode() {
+            let { data, row, column, barplot, crossTab, cramersCoef } = this.state;
+            let codeList = [];
+            let code = new com_String();
+
+            code.appendFormatLine("vp_df = {0}.dropna().copy()", data);
+
+            // Display option
+            if (barplot === true) {
+                code.appendLine();
+                code.appendLine("# Count plot");
+                code.appendLine("import seaborn as sns");
+                code.appendLine("import warnings");
+                code.appendLine("with warnings.catch_warnings():");
+                code.appendLine("    warnings.simplefilter(action='ignore', category=Warning)");
+                code.appendFormatLine("    sns.countplot(data=vp_df, x={0}, hue={1})", row, column);
+                code.appendLine("    plt.show()");
+            }
+            
+            code.appendLine("");
+            code.appendLine("# Chi-square test of independence");
+            code.appendLine("from IPython.display import display, Markdown");
+            code.appendLine("from scipy import stats");
+            code.appendFormatLine("_obs  = pd.crosstab(index=vp_df[{0}], columns=vp_df[{1}])", row, column);
+            code.appendLine("_res1 = stats.chi2_contingency(_obs)");
+            code.appendLine("_res2 = stats.chi2_contingency(_obs, lambda_='log-likelihood')");
+            
+            if (crossTab === true) {
+                code.appendLine("");
+                code.appendLine("# Cross tabulation: Count");
+                code.appendFormatLine("_dfc = pd.crosstab(index=vp_df[{0}],columns=vp_df[{1}],margins=True,margins_name='Total')", row, column);
+                code.appendLine("_dfc = _dfc.reset_index().reset_index()");
+                code.appendLine("_dfc[' '] = 'Count'");
+                code.appendLine("");
+                code.appendLine("# Cross tabulation: Expected count");
+                code.appendLine("_dfe = pd.DataFrame(_res1.expected_freq, index=_obs.index, columns=_obs.columns).round(1)");
+                code.appendLine("_dfe.loc['Total',:] = _dfe.sum(axis=0)");
+                code.appendLine("_dfe.loc[:,'Total'] = _dfe.sum(axis=1)");
+                code.appendLine("_dfe = _dfe.reset_index().reset_index()");
+                code.appendLine("_dfe[' '] = 'Expected count'");
+                code.appendLine("");
+                code.appendLine("# Cross tabulation: Count + Expected count");
+                code.appendLine("display(Markdown('### Cross tabulation'))");
+                code.appendFormatLine("display(pd.concat([_dfc, _dfe]).set_index([{0},' ']).sort_values('index').drop('index',axis=1))", row);
+            }
+
+            code.appendLine("");
+            code.appendLine("# Chi-square test");
+            code.appendLine("display(Markdown('### Chi-square test'))");
+            code.appendLine("display(pd.DataFrame(data = {'Value':[_res1.statistic,_res2.statistic,vp_df.dropna().shape[0]],");
+            code.appendLine("                             'df':[_res1.dof,_res2.dof,np.nan],");
+            code.appendLine("                             'p-value(two-sided)':[_res1.pvalue,_res2.pvalue,np.nan]},");
+            code.append("                     index= ['Pearson Chi-square','Likelihood ratio','N of valid cases']))");
+
+            if (cramersCoef === true) {
+                code.appendLine("");
+                code.appendLine("");
+                code.appendLine("# Cramers' V coefficient");
+                code.appendLine("_X2 = stats.chi2_contingency(_obs)[0]");
+                code.appendLine("_sum = _obs.sum().sum()");
+                code.appendLine("_minDim = min(_obs.shape)-1");
+                code.appendLine("display(Markdown('### Cramers V coefficient'))");
+                code.append("display(pd.DataFrame(data={'Value':np.sqrt((_X2/_sum) / _minDim)}, index=['Cramers V coefficient']))");
+            }
+            codeList.push(code.toString());
+
+            return codeList;
+        }
+
+    }
+
+    return Chi2test;
+});
\ No newline at end of file