{"id":575,"date":"2021-11-13T10:25:31","date_gmt":"2021-11-13T09:25:31","guid":{"rendered":"https:\/\/lorentzen.ch\/?p=575"},"modified":"2021-11-13T15:09:10","modified_gmt":"2021-11-13T14:09:10","slug":"random-forests-with-monotonic-constraints","status":"publish","type":"post","link":"https:\/\/lorentzen.ch\/index.php\/2021\/11\/13\/random-forests-with-monotonic-constraints\/","title":{"rendered":"Random Forests with Monotonic Constraints"},"content":{"rendered":"\n<h1 class=\"wp-block-heading\">Lost in Translation between R and Python 7<\/h1>\n\n\n\n<p>Hello random forest friends<\/p>\n\n\n\n<p>This is the next article in our series <strong>&#8220;Lost in Translation between R and Python&#8221;<\/strong>. The aim of this series is to provide high-quality R <strong>and<\/strong> Python 3 code to achieve some non-trivial tasks. If you are to learn R, check out the R tab below. Similarly, if you are to learn Python, the Python tab will be your friend.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Monotonic constraints<\/h2>\n\n\n\n<p>On ML competition platforms like <a href=\"https:\/\/kaggle.com\/\">Kaggle<\/a>, complex and unintuitively behaving models dominate. In this respect, reality is completely different. There, the majority of models do not serve as pure prediction machines but rather as fruitful source of information. Furthermore, even if used as prediction machine, the users of the models might expect a certain degree of consistency when &#8220;playing&#8221; with input values. <\/p>\n\n\n\n<p>A classic example are statistical house appraisal models. An additional bathroom or an additional square foot of ground area is expected to raise the appraisal, everything else being fixed (ceteris paribus). The user might lose trust in the model if the opposite happens.<\/p>\n\n\n\n<p>One way to enforce such consistency is to monitor the signs of coefficients of a linear regression model. Another useful strategy is to impose <strong>monotonicity constraints<\/strong> on selected model effects.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Trees and monotonic constraints<\/h2>\n\n\n\n<p>Monotonicity constraints are especially simple to implement for decision trees. The rule is basically as follows: <br><strong>If a monotonicity constraint would be violated by a split on feature <em>X<\/em>, it is rejected.<\/strong> (Or a large penalty is subtracted from the corresponding split gain.) This will imply monotonic behavior of predictions in <em>X<\/em>, keeping all other features fixed. <\/p>\n\n\n\n<p>Tree ensembles like boosted trees or random forests will automatically inherit this property.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Boosted trees<\/h3>\n\n\n\n<p>Most implementations of boosted trees offer monotonicity constraints. Here is a selection:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>R &amp; Python: <a href=\"https:\/\/xgboost.readthedocs.io\/en\/latest\/tutorials\/monotonic.html\">XGBoost<\/a><\/li><li>R &amp; Python: <a href=\"https:\/\/lightgbm.readthedocs.io\/en\/latest\/Parameters.html\">LightGBM<\/a><\/li><li>R &amp; Python: <a href=\"https:\/\/catboost.ai\/docs\/concepts\/cli-reference_train-model.html\">CatBoost<\/a><\/li><li>Python: <a href=\"https:\/\/scikit-learn.org\/stable\/auto_examples\/ensemble\/plot_monotonic_constraints.html\">Scikit-Learn histogram gradient booster<\/a><\/li><li>R: <a href=\"https:\/\/CRAN.R-project.org\/package=gbm\">gbm<\/a><\/li><\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">What about random forests?<\/h3>\n\n\n\n<p>Unfortunately, the picture is completely different for random forests. At the time of writing, I am not aware of any random forest implementation in R or Python offering this useful feature.<\/p>\n\n\n\n<p><strong>Some options<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Implement monotonic constrainted random forests from scratch. <\/li><li>Ask for this feature in existing implementations.<\/li><li>Be creative and use XGBoost to emulate random forests.<\/li><\/ol>\n\n\n\n<p>For the moment, let&#8217;s stick to option 3. In our <a href=\"https:\/\/lorentzen.ch\/index.php\/2021\/05\/21\/strong-random-forests-with-xgboost\/\">last R &lt;-&gt; Python blog post<\/a>, we demonstrated that <strong>XGBoost&#8217;s random forest mode works essentially as good<\/strong> as standard random forest implementations, at least in regression settings and using sensible defaults.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Warning: <strong>Be careful with imposing monotonicity constraints<\/strong> <\/h3>\n\n\n\n<p>Ask yourself: does the constraint really make sense for all possible values of other features? You will see that the answer is often &#8220;no&#8221;.<\/p>\n\n\n\n<p>An example: If your house price model uses the features &#8220;number of rooms&#8221; and &#8220;living area&#8221;, then a monotonic constraint on &#8220;living area&#8221; might make sense (given any number of rooms), while such constraint would be non-sensical for the number of rooms. Why? Because having six rooms in a 1200 square feet home is not necessarily better than having just five rooms <em>in an equally sized home<\/em>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Let&#8217;s try it out<\/h2>\n\n\n\n<p>We use a nice dataset containing information on over 20,000 sold houses in Kings County. Along with the sale price, different features describe the size and location of the properties. The dataset is available on <a href=\"https:\/\/www.openml.org\/d\/42092\">OpenML.org<\/a> with ID 42092.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"224\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/05\/image-1024x224.png\" alt=\"\" class=\"wp-image-418\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/05\/image-1024x224.png 1024w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/05\/image-300x66.png 300w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/05\/image-768x168.png 768w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/05\/image.png 1146w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Some rows and columns from the Kings County house dataset.<\/figcaption><\/figure>\n\n\n\n<p>The following R and Python codes <\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>fetch the data, <\/li><li>prepare the ML setting,<\/li><li>fit unconstrained XGBoost random forests using log sales price as response,<\/li><li>and visualize the effect of log ground area by individual conditional expectation (ICE) curves. <\/li><\/ul>\n\n\n\n<p><em>An ICE curve for variable X shows how the prediction of one specific observation changes if the value of X changes. Repeating this for multiple observations gives an idea of the effect of X. The average over multiple ICE curves produces the famous partial dependent plot.<\/em><\/p>\n\n\n<div class=\"wp-block-ub-tabbed-content wp-block-ub-tabbed-content-holder wp-block-ub-tabbed-content-horizontal-holder-mobile wp-block-ub-tabbed-content-horizontal-holder-tablet\" id=\"ub-tabbed-content-0ad96657-0431-4134-b8a6-13f7681b100e\" style=\"\">\n\t\t\t<div class=\"wp-block-ub-tabbed-content-tab-holder horizontal-tab-width-mobile horizontal-tab-width-tablet\">\n\t\t\t\t<div role=\"tablist\" class=\"wp-block-ub-tabbed-content-tabs-title wp-block-ub-tabbed-content-tabs-title-mobile-horizontal-tab wp-block-ub-tabbed-content-tabs-title-tablet-horizontal-tab\" style=\"justify-content: flex-start; \"><div role=\"tab\" id=\"ub-tabbed-content-0ad96657-0431-4134-b8a6-13f7681b100e-tab-0\" aria-controls=\"ub-tabbed-content-0ad96657-0431-4134-b8a6-13f7681b100e-panel-0\" aria-selected=\"true\" class=\"wp-block-ub-tabbed-content-tab-title-wrap active\" style=\"--ub-tabbed-title-background-color: #6d6d6d; --ub-tabbed-active-title-color: inherit; --ub-tabbed-active-title-background-color: #6d6d6d; text-align: center; \" tabindex=\"-1\">\n\t\t\t\t<div class=\"wp-block-ub-tabbed-content-tab-title\">R<\/div>\n\t\t\t<\/div><div role=\"tab\" id=\"ub-tabbed-content-0ad96657-0431-4134-b8a6-13f7681b100e-tab-1\" aria-controls=\"ub-tabbed-content-0ad96657-0431-4134-b8a6-13f7681b100e-panel-1\" aria-selected=\"false\" class=\"wp-block-ub-tabbed-content-tab-title-wrap\" style=\"--ub-tabbed-active-title-color: inherit; --ub-tabbed-active-title-background-color: #6d6d6d; text-align: center; \" tabindex=\"-1\">\n\t\t\t\t<div class=\"wp-block-ub-tabbed-content-tab-title\">Python<\/div>\n\t\t\t<\/div><\/div>\n\t\t\t<\/div>\n\t\t\t<div class=\"wp-block-ub-tabbed-content-tabs-content\" style=\"\"><div role=\"tabpanel\" class=\"wp-block-ub-tabbed-content-tab-content-wrap active\" id=\"ub-tabbed-content-0ad96657-0431-4134-b8a6-13f7681b100e-panel-0\" aria-labelledby=\"ub-tabbed-content-0ad96657-0431-4134-b8a6-13f7681b100e-tab-0\" tabindex=\"0\">\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting='{\"showPanel\":true,\"languageLabel\":\"language\",\"fullScreenButton\":true,\"copyButton\":true,\"mode\":\"r\",\"mime\":\"text\/x-rsrc\",\"theme\":\"material\",\"lineNumbers\":false,\"styleActiveLine\":false,\"lineWrapping\":false,\"readOnly\":true,\"fileName\":\"\",\"language\":\"R\",\"maxHeight\":\"400px\",\"modeName\":\"r\"}'>library(farff)\nlibrary(OpenML)\nlibrary(dplyr)\nlibrary(xgboost)\n\nset.seed(83454)\n\nrmse &lt;- function(y, pred) {\n  sqrt(mean((y-pred)^2))\n}\n\n# Load King Country house prices dataset on OpenML\n# ID 42092, https:\/\/www.openml.org\/d\/42092\ndf &lt;- getOMLDataSet(data.id = 42092)$data\nhead(df)\n\n# Prepare\ndf &lt;- df %&gt;%\n  mutate(\n    log_price = log(price),\n    log_sqft_lot = log(sqft_lot),\n    year = as.numeric(substr(date, 1, 4)),\n    building_age = year - yr_built,\n    zipcode = as.integer(as.character(zipcode))\n  )\n\n# Define response and features\ny &lt;- \"log_price\"\nx &lt;- c(\"grade\", \"year\", \"building_age\", \"sqft_living\",\n       \"log_sqft_lot\", \"bedrooms\", \"bathrooms\", \"floors\", \"zipcode\",\n       \"lat\", \"long\", \"condition\", \"waterfront\")\n\n# random split\nix &lt;- sample(nrow(df), 0.8 * nrow(df))\ny_test &lt;- df[[y]][-ix]\n\n# Fit untuned, but good(!) XGBoost random forest\ndtrain &lt;- xgb.DMatrix(data.matrix(df[ix, x]),\n                      label = df[ix, y])\n\nparams &lt;- list(\n  objective = \"reg:squarederror\",\n  learning_rate = 1,\n  num_parallel_tree = 500,\n  subsample = 0.63,\n  colsample_bynode = 1\/3,\n  reg_lambda = 0,\n  max_depth = 20,\n  min_child_weight = 2\n)\n\nsystem.time( # 25 s\n  unconstrained &lt;- xgb.train(\n    params,\n    data = dtrain,\n    nrounds = 1,\n    verbose = 0\n  )\n)\n\npred &lt;- predict(unconstrained, data.matrix(df[-ix, x]))\n\n# Test RMSE: 0.172\nrmse(y_test, pred)\n\n# ICE curves via our flashlight package\nlibrary(flashlight)\n\npred_xgb &lt;- function(m, X) predict(m, data.matrix(X[, x]))\n\nfl &lt;- flashlight(\n  model = unconstrained,\n  label = \"unconstrained\",\n  data = df[ix, ],\n  predict_function = pred_xgb\n)\n\nlight_ice(fl, v = \"log_sqft_lot\", indices = 1:9,\n          evaluate_at = seq(7, 11, by = 0.1)) %&gt;%\n  plot()<\/pre><\/div>\n\n<\/div><div role=\"tabpanel\" class=\"wp-block-ub-tabbed-content-tab-content-wrap ub-hide\" id=\"ub-tabbed-content-0ad96657-0431-4134-b8a6-13f7681b100e-panel-1\" aria-labelledby=\"ub-tabbed-content-0ad96657-0431-4134-b8a6-13f7681b100e-tab-1\" tabindex=\"0\">\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting='{\"showPanel\":true,\"languageLabel\":\"language\",\"fullScreenButton\":true,\"copyButton\":true,\"mode\":\"python\",\"mime\":\"text\/x-python\",\"theme\":\"material\",\"lineNumbers\":false,\"styleActiveLine\":false,\"lineWrapping\":false,\"readOnly\":true,\"fileName\":\"\",\"language\":\"Python\",\"maxHeight\":\"400px\",\"modeName\":\"python\"}'># Imports\nimport numpy as np\nfrom sklearn.datasets import fetch_openml\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error\nfrom sklearn.inspection import PartialDependenceDisplay\nfrom xgboost import XGBRFRegressor\n\n# Fetch data from OpenML\ndf = fetch_openml(data_id=42092, as_frame=True)[\"frame\"]\n\n# Prepare data\ndf = df.assign(\n    year=lambda x: x.date.str[0:4].astype(int),\n    zipcode=lambda x: x.zipcode.astype(int),\n    log_sqft_lot=lambda x: np.log(x.sqft_lot),\n    building_age=lambda x: x.year - x.yr_built,\n)\n\n# Feature list\nxvars = [\n    \"grade\",\n    \"year\",\n    \"building_age\",\n    \"sqft_living\",\n    \"log_sqft_lot\",\n    \"bedrooms\",\n    \"bathrooms\",\n    \"floors\",\n    \"zipcode\",\n    \"lat\",\n    \"long\",\n    \"condition\",\n    \"waterfront\",\n]\n\n# Data split\ny_train, y_test, X_train, X_test = train_test_split(\n    np.log(df[\"price\"]), df[xvars], train_size=0.8, random_state=766\n)\n\n# Modeling - wall time: 39 seconds\nparam_dict = dict(\n    n_estimators=500,\n    max_depth=20,\n    learning_rate=1,\n    subsample=0.63,\n    colsample_bynode=1 \/ 3,\n    reg_lambda=0,\n    objective=\"reg:squarederror\",\n    min_child_weight=2,\n)\n\nunconstrained = XGBRFRegressor(**param_dict).fit(X_train, y_train)\n\n# Test RMSE 0.176\npred = unconstrained.predict(X_test)\nprint(f\"RMSE: {mean_squared_error(y_test, pred, squared=False):.03f}\")\n\n# ICE and PDP - wall time: 47 seconds\nPartialDependenceDisplay.from_estimator(\n    unconstrained,\n    X=X_train,\n    features=[\"log_sqft_lot\"],\n    kind=\"both\",\n    subsample=20,\n    random_state=1,\n)<\/pre><\/div>\n\n<\/div><\/div>\n\t\t<\/div>\n\n\n<p> <\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"546\" height=\"547\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/11\/ice-1.png\" alt=\"\" class=\"wp-image-686\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/11\/ice-1.png 546w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/11\/ice-1-300x300.png 300w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/11\/ice-1-150x150.png 150w\" sizes=\"auto, (max-width: 546px) 100vw, 546px\" \/><figcaption>Figure 1 (R output): ICE curves of log(ground area) for the first nine observations. Many non-monotonic parts are visible.<\/figcaption><\/figure>\n\n\n\n<p>We clearly see many non-monotonic (and in this case counterintuitive) ICE curves.<\/p>\n\n\n\n<p>What would a model give with monotonically increasing constraint on the ground area?<\/p>\n\n\n<div class=\"wp-block-ub-tabbed-content wp-block-ub-tabbed-content-holder wp-block-ub-tabbed-content-horizontal-holder-mobile wp-block-ub-tabbed-content-horizontal-holder-tablet\" id=\"ub-tabbed-content-27c07e9d-d074-4322-8733-36330868b79b\" style=\"\">\n\t\t\t<div class=\"wp-block-ub-tabbed-content-tab-holder horizontal-tab-width-mobile horizontal-tab-width-tablet\">\n\t\t\t\t<div role=\"tablist\" class=\"wp-block-ub-tabbed-content-tabs-title wp-block-ub-tabbed-content-tabs-title-mobile-horizontal-tab wp-block-ub-tabbed-content-tabs-title-tablet-horizontal-tab\" style=\"justify-content: flex-start; \"><div role=\"tab\" id=\"ub-tabbed-content-27c07e9d-d074-4322-8733-36330868b79b-tab-0\" aria-controls=\"ub-tabbed-content-27c07e9d-d074-4322-8733-36330868b79b-panel-0\" aria-selected=\"true\" class=\"wp-block-ub-tabbed-content-tab-title-wrap active\" style=\"--ub-tabbed-title-background-color: #6d6d6d; --ub-tabbed-active-title-color: inherit; --ub-tabbed-active-title-background-color: #6d6d6d; text-align: center; \" tabindex=\"-1\">\n\t\t\t\t<div class=\"wp-block-ub-tabbed-content-tab-title\">R<\/div>\n\t\t\t<\/div><div role=\"tab\" id=\"ub-tabbed-content-27c07e9d-d074-4322-8733-36330868b79b-tab-1\" aria-controls=\"ub-tabbed-content-27c07e9d-d074-4322-8733-36330868b79b-panel-1\" aria-selected=\"false\" class=\"wp-block-ub-tabbed-content-tab-title-wrap\" style=\"--ub-tabbed-active-title-color: inherit; --ub-tabbed-active-title-background-color: #6d6d6d; text-align: center; \" tabindex=\"-1\">\n\t\t\t\t<div class=\"wp-block-ub-tabbed-content-tab-title\">Python<\/div>\n\t\t\t<\/div><\/div>\n\t\t\t<\/div>\n\t\t\t<div class=\"wp-block-ub-tabbed-content-tabs-content\" style=\"\"><div role=\"tabpanel\" class=\"wp-block-ub-tabbed-content-tab-content-wrap active\" id=\"ub-tabbed-content-27c07e9d-d074-4322-8733-36330868b79b-panel-0\" aria-labelledby=\"ub-tabbed-content-27c07e9d-d074-4322-8733-36330868b79b-tab-0\" tabindex=\"0\">\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting='{\"showPanel\":true,\"languageLabel\":\"language\",\"fullScreenButton\":true,\"copyButton\":true,\"mode\":\"r\",\"mime\":\"text\/x-rsrc\",\"theme\":\"material\",\"lineNumbers\":false,\"styleActiveLine\":false,\"lineWrapping\":false,\"readOnly\":true,\"fileName\":\"\",\"language\":\"R\",\"maxHeight\":\"400px\",\"modeName\":\"r\"}'># Monotonic increasing constraint\n(params$monotone_constraints &lt;- 1 * (x == \"log_sqft_lot\"))\n\nsystem.time( #  179s\n  monotonic &lt;- xgb.train(\n    params,\n    data = dtrain,\n    nrounds = 1,\n    verbose = 0\n  )\n)\n\npred &lt;- predict(monotonic, data.matrix(df[-ix, x]))\n\n# Test RMSE: 0.176\nrmse(y_test, pred)\n\nfl_m &lt;- flashlight(\n  model = monotonic,\n  label = \"monotonic\",\n  data = df[ix, ],\n  predict_function = pred_xgb\n)\n\nlight_ice(fl_m, v = \"log_sqft_lot\", indices = 1:9,\n          evaluate_at = seq(7, 11, by = 0.1)) %&gt;%\n  plot()<\/pre><\/div>\n\n<\/div><div role=\"tabpanel\" class=\"wp-block-ub-tabbed-content-tab-content-wrap ub-hide\" id=\"ub-tabbed-content-27c07e9d-d074-4322-8733-36330868b79b-panel-1\" aria-labelledby=\"ub-tabbed-content-27c07e9d-d074-4322-8733-36330868b79b-tab-1\" tabindex=\"0\">\n\n<div class=\"wp-block-codemirror-blocks-code-block code-block\"><pre class=\"CodeMirror\" data-setting='{\"showPanel\":true,\"languageLabel\":\"language\",\"fullScreenButton\":true,\"copyButton\":true,\"mode\":\"python\",\"mime\":\"text\/x-python\",\"theme\":\"material\",\"lineNumbers\":false,\"styleActiveLine\":false,\"lineWrapping\":false,\"readOnly\":true,\"fileName\":\"\",\"language\":\"Python\",\"maxHeight\":\"400px\",\"modeName\":\"python\"}'># One needs to pass the constraints as single string, which is rather ugly\nmc = \"(\" + \",\".join([str(int(x == \"log_sqft_lot\")) for x in xvars]) + \")\"\nprint(mc)\n\n# Modeling - wall time 49 seconds\nconstrained = XGBRFRegressor(monotone_constraints=mc, **param_dict)\nconstrained.fit(X_train, y_train)\n\n# Test RMSE: 0.178\npred = constrained.predict(X_test)\nprint(f\"RMSE: {mean_squared_error(y_test, pred, squared=False):.03f}\")\n\n# ICE and PDP - wall time 39 seconds\nPartialDependenceDisplay.from_estimator(\n    constrained,\n    X=X_train,\n    features=[\"log_sqft_lot\"],\n    kind=\"both\",\n    subsample=20,\n    random_state=1,\n)<\/pre><\/div>\n\n<\/div><\/div>\n\t\t<\/div>\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"546\" height=\"547\" src=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/11\/ice_m.png\" alt=\"\" class=\"wp-image-687\" srcset=\"https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/11\/ice_m.png 546w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/11\/ice_m-300x300.png 300w, https:\/\/lorentzen.ch\/wp-content\/uploads\/2021\/11\/ice_m-150x150.png 150w\" sizes=\"auto, (max-width: 546px) 100vw, 546px\" \/><figcaption>Figure 2 (R output): ICE curves of the same observations as in Figure 1, but now with monotonic constraint. All curves are monotonically increasing.<\/figcaption><\/figure><\/div>\n\n\n\n<p><strong>We see:<\/strong><\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>It works! Each ICE curve in log(lot area) is monotonically increasing. This means that predictions are monotonically increasing in lot area, keeping all other feature values fixed.<\/li><li>The model performance is slightly worse. This is the price paid for receiving a more intuitive behaviour in an important feature.<\/li><li>In Python, both models take about the same time to fit (30-40 s on a 4 core i7 CPU laptop). Curiously, in R, the constrained model takes about six times longer to fit than the unconstrained one (170 s vs 30 s).<\/li><\/ol>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<ul class=\"wp-block-list\"><li>Monotonic constraints help to create intuitive models.<\/li><li>Unfortunately, as per now, native random forest implementations do not offer such constraints.<\/li><li>Using XGBoost&#8217;s random forest mode is a temporary solution until native random forest implementations add this feature.<\/li><li>Be careful to add too many constraints: does a constraint really make sense for all other (fixed) choices of feature values?<\/li><\/ul>\n\n\n\n<p>The Python notebook and R code can be found at:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/github.com\/lorentzenchr\/notebooks\/blob\/master\/blogposts\/2021-11-13%20random%20forests%20monotone.ipynb\">https:\/\/github.com\/lorentzenchr\/notebooks\/blob\/master\/blogposts\/2021-11-13%20random%20forests%20monotone.ipynb<\/a><\/li><li><a href=\"https:\/\/github.com\/lorentzenchr\/notebooks\/blob\/master\/blogposts\/2021-11-13%20random%20forests%20monotone.r\">https:\/\/github.com\/lorentzenchr\/notebooks\/blob\/master\/blogposts\/2021-11-13%20random%20forests%20monotone.r<\/a><\/li><\/ul>\n","protected":false},"excerpt":{"rendered":"<p>&#8220;R <-> Python&#8221; continued&#8230; Random forests with monotonic constraints<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16],"tags":[10,6,5],"class_list":["post-575","post","type-post","status-publish","format-standard","hentry","category-machine-learning","tag-lost-in-translation","tag-python","tag-r"],"featured_image_src":null,"author_info":{"display_name":"Michael Mayer","author_link":"https:\/\/lorentzen.ch\/index.php\/author\/michael\/"},"_links":{"self":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts\/575","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/comments?post=575"}],"version-history":[{"count":34,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts\/575\/revisions"}],"predecessor-version":[{"id":688,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/posts\/575\/revisions\/688"}],"wp:attachment":[{"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/media?parent=575"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/categories?post=575"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/lorentzen.ch\/index.php\/wp-json\/wp\/v2\/tags?post=575"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}