From 7dd35106443826199a92fffd74a69ee4c4c19a43 Mon Sep 17 00:00:00 2001 From: "Steven Paul Sanderson II, MPH" Date: Thu, 16 Nov 2023 08:31:25 -0500 Subject: [PATCH] new post --- .../index/execute-results/html.json | 20 + .../index/figure-html/unnamed-chunk-1-1.png | Bin 0 -> 19402 bytes .../index/figure-html/unnamed-chunk-1-2.png | Bin 0 -> 20232 bytes .../htmlwidgets-1.6.2/htmlwidgets.js | 901 +++++++++++++++++ .../site_libs/plotly-binding-4.10.3/plotly.js | 941 ++++++++++++++++++ docs/index.html | 513 +++++----- docs/index.xml | 693 ++++++++----- docs/listings.json | 1 + docs/posts/2011-11-16/index.html | 914 +++++++++++++++++ .../figure-html/unnamed-chunk-1-1.png | Bin 0 -> 19402 bytes .../figure-html/unnamed-chunk-1-2.png | Bin 0 -> 20232 bytes docs/search.json | 79 +- .../htmlwidgets-1.6.2/htmlwidgets.js | 901 +++++++++++++++++ .../site_libs/plotly-binding-4.10.3/plotly.js | 941 ++++++++++++++++++ docs/sitemap.xml | 6 +- posts/2011-11-16/index.qmd | 108 ++ 16 files changed, 5524 insertions(+), 494 deletions(-) create mode 100644 _freeze/posts/2011-11-16/index/execute-results/html.json create mode 100644 _freeze/posts/2011-11-16/index/figure-html/unnamed-chunk-1-1.png create mode 100644 _freeze/posts/2011-11-16/index/figure-html/unnamed-chunk-1-2.png create mode 100644 _freeze/site_libs/htmlwidgets-1.6.2/htmlwidgets.js create mode 100644 _freeze/site_libs/plotly-binding-4.10.3/plotly.js create mode 100644 docs/posts/2011-11-16/index.html create mode 100644 docs/posts/2011-11-16/index_files/figure-html/unnamed-chunk-1-1.png create mode 100644 docs/posts/2011-11-16/index_files/figure-html/unnamed-chunk-1-2.png create mode 100644 docs/site_libs/htmlwidgets-1.6.2/htmlwidgets.js create mode 100644 docs/site_libs/plotly-binding-4.10.3/plotly.js create mode 100644 posts/2011-11-16/index.qmd diff --git a/_freeze/posts/2011-11-16/index/execute-results/html.json b/_freeze/posts/2011-11-16/index/execute-results/html.json new file mode 100644 index 00000000..ae4f0f51 --- /dev/null +++ b/_freeze/posts/2011-11-16/index/execute-results/html.json @@ -0,0 +1,20 @@ +{ + "hash": "51c17ad5bd721875dfb0f3bd74939d65", + "result": { + "markdown": "---\ntitle: \"{healthyR.ts} New Features: Unlocking More Power\"\nauthor: \"Steven P. Sanderson II, MPH\"\ndate: \"2023-11-16\"\ncategories: [rtip, healthyrts, timeseries]\n---\n\n\n# New Features: Unlocking More Power\n\nMy R package `{healthyR.ts}` has been updated to version 0.3.0; you can install it from either CRAN, r-universe or GitHub. Let's go over some of the changes and improvements.\n\n# News\n\n## 1. `util_log_ts()` - Logging Time Series Data\n\nOne of the standout additions is the introduction of `util_log_ts()`. This function seems like a game-changer, providing a streamlined way to log time series data. This is incredibly useful, especially when dealing with extensive datasets, making the whole process more efficient and user-friendly. This is a helper function for `auto_stationarize()`.\n\n## 2. `util_singlediff_ts()` - Single Differences for Time Series\n\nThe addition of `util_singlediff_ts()` expands the toolkit, offering a function dedicated to handling single differences in time series data. This is valuable for various applications, such as identifying trends or preparing data for further analysis. This is a helper function for `auto_stationarize()`.\n\n## 3. `util_doublediff_ts()` - Double Differences for Time Series\n\nBuilding on the concept of differencing, `util_doublediff_ts()` seems to provide a higher level of sophistication, allowing users to perform double differences on time series data. This could be pivotal in cases where a more refined analysis is required. This is a helper function for `auto_stationarize()`.\n\n## 4. `util_difflog_ts()` - Combining Differences and Log Transformation\n\nThe fusion of differencing and log transformation in `util_difflog_ts()` is a remarkable addition. This could be particularly beneficial in scenarios where both operations are needed to unlock deeper insights from the time series data. This is a helper function for `auto_stationarize()`.\n\n## 5. `util_doubledifflog_ts()` - Double Differences with Log Transformation\n\nThe introduction of `util_doubledifflog_ts()` appears to take things a step further by combining double differences and log transformation. This function seems poised to provide a comprehensive solution for users dealing with complex time series data. This is a helper function for `auto_stationarize()`.\n\n# Minor Fixes and Improvements: Polishing the Experience\n\n## 1. Attributes Enhancement in `ts_growth_rate_vec()`\nThe attention to detail is evident with the addition of attributes to the output of `ts_growth_rate_vec()`. This enhancement not only improves the clarity of results but also contributes to a more informative and user-friendly experience.\n\n## 2. Refinement of `auto_stationarize()` in Response to User Feedback\n\nUpdates to `auto_stationarize()` based on user feedback (Fix #481 #483) demonstrate a commitment to refining existing features. This responsiveness to the community's needs is commendable and ensures that the package evolves in sync with user expectations. It has taken all of the `util_` transforms mentioned above in order to improve it's functionality.\n\n## 3. Integration with `auto_arima` Engine in `ts_auto_arima()`\n\nThe integration of `ts_auto_arima()` with the parsnip engine of `auto_arima` is a notable improvement. This update, triggered when `.tune` is set to `FALSE`, aligns the package with cutting-edge tools, potentially enhancing the efficiency and accuracy of time series modeling.\n\nIn conclusion, the release of healthyR.ts version 0.3.0 is an exciting leap forward. The new features introduce powerful capabilities, while the minor fixes and improvements showcase a commitment to providing a robust and user-friendly package. Users can look forward to a more versatile and refined experience in time series analysis. Great job on this release, and I'm sure the community is eager to explore these enhancements!\n\n# Examples\n\nLet's see how the main functions now behave.\n\n## `auto_stationarize()`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(healthyR.ts)\n\nauto_stationarize(AirPassengers)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nThe time series is already stationary via ts_adf_test().\n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\n Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec\n1949 112 118 132 129 121 135 148 148 136 119 104 118\n1950 115 126 141 135 125 149 170 170 158 133 114 140\n1951 145 150 178 163 172 178 199 199 184 162 146 166\n1952 171 180 193 181 183 218 230 242 209 191 172 194\n1953 196 196 236 235 229 243 264 272 237 211 180 201\n1954 204 188 235 227 234 264 302 293 259 229 203 229\n1955 242 233 267 269 270 315 364 347 312 274 237 278\n1956 284 277 317 313 318 374 413 405 355 306 271 306\n1957 315 301 356 348 355 422 465 467 404 347 305 336\n1958 340 318 362 348 363 435 491 505 404 359 310 337\n1959 360 342 406 396 420 472 548 559 463 407 362 405\n1960 417 391 419 461 472 535 622 606 508 461 390 432\n```\n:::\n\n```{.r .cell-code}\nauto_stationarize(BJsales)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nThe time series is not stationary. Attempting to make it stationary...\n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\n$stationary_ts\nTime Series:\nStart = 3 \nEnd = 150 \nFrequency = 1 \n [1] 0.5 -0.4 0.6 1.1 -2.8 3.0 -1.1 0.6 -0.5 -0.5 0.1 2.0 -0.6 0.8 1.2\n [16] -3.4 -0.7 -0.3 1.7 3.0 -3.2 0.9 2.2 -2.5 -0.4 2.6 -4.3 2.0 -3.1 2.7\n [31] -2.1 0.1 2.1 -0.2 -2.2 0.6 1.0 -2.6 3.0 0.3 0.2 -0.8 1.0 0.0 3.2\n [46] -2.2 -4.7 1.2 0.8 -0.6 -0.4 0.6 1.0 -1.6 -0.1 3.4 -0.9 -1.7 -0.5 0.8\n [61] 2.4 -1.9 0.6 -2.2 2.6 -0.1 -2.7 1.7 -0.3 1.9 -2.7 1.1 -0.6 0.9 0.0\n [76] 1.8 -0.5 -0.4 -1.2 2.6 -1.8 1.7 -0.9 0.6 -0.4 3.0 -2.8 3.1 -2.3 -1.1\n [91] 2.1 -0.3 -1.7 -0.8 -0.4 1.1 -1.5 0.3 1.4 -2.0 1.3 -0.3 0.4 -3.5 1.1\n[106] 2.6 0.4 -1.3 2.0 -1.6 0.6 -0.1 -1.4 1.6 1.6 -3.4 1.7 -2.2 2.1 -2.0\n[121] -0.2 0.2 0.7 -1.4 1.8 -0.1 -0.7 0.4 0.4 1.0 -2.4 1.0 -0.4 0.8 -1.0\n[136] 1.4 -1.2 1.1 -0.9 0.5 1.9 -0.6 0.3 -1.4 -0.9 -0.5 1.4 0.1\n\n$ndiffs\n[1] 1\n\n$adf_stats\n$adf_stats$test_stat\n[1] -6.562008\n\n$adf_stats$p_value\n[1] 0.01\n\n\n$trans_type\n[1] \"double_diff\"\n\n$ret\n[1] TRUE\n```\n:::\n\n```{.r .cell-code}\nplot.ts(auto_stationarize(BJsales)$stationary_ts)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nThe time series is not stationary. Attempting to make it stationary...\n```\n:::\n\n::: {.cell-output-display}\n![](index_files/figure-html/unnamed-chunk-1-1.png){width=672}\n:::\n\n```{.r .cell-code}\nauto_stationarize(BJsales.lead)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nThe time series is not stationary. Attempting to make it stationary...\n```\n:::\n\n::: {.cell-output .cell-output-stdout}\n```\n$stationary_ts\nTime Series:\nStart = 2 \nEnd = 150 \nFrequency = 1 \n [1] 0.06 0.25 -0.57 0.58 -0.20 0.23 -0.04 -0.19 0.03 0.42 0.04 0.24\n [13] 0.34 -0.46 -0.18 -0.08 0.29 0.56 -0.37 0.20 0.54 -0.31 0.03 0.52\n [25] -0.70 0.35 -0.63 0.44 -0.38 -0.01 0.22 0.10 -0.50 0.01 0.30 -0.76\n [37] 0.52 0.15 0.06 -0.10 0.21 -0.01 0.70 -0.22 -0.76 0.06 0.02 -0.17\n [49] -0.08 0.01 0.11 -0.39 0.01 0.50 -0.02 -0.37 -0.13 0.05 0.54 -0.46\n [61] 0.25 -0.52 0.44 0.02 -0.47 0.11 0.06 0.25 -0.35 0.00 -0.06 0.21\n [73] -0.09 0.36 0.09 -0.04 -0.20 0.44 -0.23 0.40 -0.01 0.17 0.08 0.58\n [85] -0.27 0.79 -0.21 0.02 0.30 0.28 -0.27 -0.01 0.03 0.16 -0.28 0.15\n [97] 0.26 -0.36 0.32 -0.11 0.22 -0.65 0.00 0.47 0.16 -0.19 0.48 -0.26\n[109] 0.21 0.00 -0.20 0.35 0.38 -0.48 0.20 -0.32 0.43 -0.50 0.12 -0.17\n[121] 0.15 -0.36 0.35 -0.03 -0.18 0.16 0.07 0.21 -0.50 0.23 -0.13 0.14\n[133] -0.15 0.19 -0.24 0.26 -0.22 0.17 0.37 -0.06 0.29 -0.34 -0.12 -0.16\n[145] 0.25 0.08 -0.07 0.26 -0.37\n\n$ndiffs\n[1] 1\n\n$adf_stats\n$adf_stats$test_stat\n[1] -4.838625\n\n$adf_stats$p_value\n[1] 0.01\n\n\n$trans_type\n[1] \"diff\"\n\n$ret\n[1] TRUE\n```\n:::\n\n```{.r .cell-code}\nplot.ts(auto_stationarize(BJsales.lead)$stationary_ts)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\nThe time series is not stationary. Attempting to make it stationary...\n```\n:::\n\n::: {.cell-output-display}\n![](index_files/figure-html/unnamed-chunk-1-2.png){width=672}\n:::\n:::\n\n\n## `ts_auto_arima()` \n\nThis use to only use the Arima engine if the `.tune` parameter was set to FALSE, thus it would many times give a simple straight line forecast. This was changed to make the engine auto_arima if `.tune` is set to FALSE.\n\n\n::: {.cell}\n\n```{.r .cell-code}\nlibrary(timetk)\nlibrary(dplyr)\nlibrary(modeltime)\n\ndata <- AirPassengers |>\n ts_to_tbl() |>\n select(-index)\n\nsplits <- time_series_split(\n data\n , date_col\n , assess = 12\n , skip = 3\n , cumulative = TRUE\n)\n\nts_aa <- ts_auto_arima(\n .data = data,\n .num_cores = 2,\n .date_col = date_col,\n .value_col = value,\n .rsamp_obj = splits,\n .formula = value ~ .,\n .grid_size = 5,\n .cv_slice_limit = 2,\n .tune = FALSE\n)\n\nts_aa$recipe_info\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$recipe_call\nrecipe(.data = data, .date_col = date_col, .value_col = value, \n .formula = value ~ ., .rsamp_obj = splits, .tune = FALSE, \n .grid_size = 5, .num_cores = 2, .cv_slice_limit = 2)\n\n$recipe_syntax\n[1] \"ts_arima_recipe <-\" \n[2] \"\\n recipe(.data = data, .date_col = date_col, .value_col = value, .formula = value ~ \\n ., .rsamp_obj = splits, .tune = FALSE, .grid_size = 5, .num_cores = 2, \\n .cv_slice_limit = 2)\"\n\n$rec_obj\n```\n:::\n\n```{.r .cell-code}\nts_aa$model_info\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$model_spec\nARIMA Regression Model Specification (regression)\n\nComputational engine: auto_arima \n\n\n$wflw\n══ Workflow ════════════════════════════════════════════════════════════════════\nPreprocessor: Recipe\nModel: arima_reg()\n\n── Preprocessor ────────────────────────────────────────────────────────────────\n0 Recipe Steps\n\n── Model ───────────────────────────────────────────────────────────────────────\nARIMA Regression Model Specification (regression)\n\nComputational engine: auto_arima \n\n\n$fitted_wflw\n══ Workflow [trained] ══════════════════════════════════════════════════════════\nPreprocessor: Recipe\nModel: arima_reg()\n\n── Preprocessor ────────────────────────────────────────────────────────────────\n0 Recipe Steps\n\n── Model ───────────────────────────────────────────────────────────────────────\nSeries: outcome \nARIMA(1,1,0)(0,1,0)[12] \n\nCoefficients:\n ar1\n -0.2431\ns.e. 0.0894\n\nsigma^2 = 109.8: log likelihood = -447.95\nAIC=899.9 AICc=900.01 BIC=905.46\n\n$was_tuned\n[1] \"not_tuned\"\n```\n:::\n\n```{.r .cell-code}\nts_aa$model_calibration\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n$plot\n\n$calibration_tbl\n# Modeltime Table\n# A tibble: 1 × 5\n .model_id .model .model_desc .type .calibration_data\n \n1 1 ARIMA(1,1,0)(0,1,0)[12] Test \n\n$model_accuracy\n# A tibble: 1 × 9\n .model_id .model_desc .type mae mape mase smape rmse rsq\n \n1 1 ARIMA(1,1,0)(0,1,0)[12] Test 18.5 4.18 0.384 4.03 23.9 0.955\n```\n:::\n\n```{.r .cell-code}\nts_aa$model_calibration$plot\n```\n\n::: {.cell-output-display}\n```{=html}\n
\n\n```\n:::\n:::\n\n\nFinally enhancement to add attributes to `ts_growth_rate_vec()`\n\n\n::: {.cell}\n\n```{.r .cell-code}\nts_growth_rate_vec(AirPassengers)\n```\n\n::: {.cell-output .cell-output-stdout}\n```\n [1] NA 5.3571429 11.8644068 -2.2727273 -6.2015504 11.5702479\n [7] 9.6296296 0.0000000 -8.1081081 -12.5000000 -12.6050420 13.4615385\n [13] -2.5423729 9.5652174 11.9047619 -4.2553191 -7.4074074 19.2000000\n [19] 14.0939597 0.0000000 -7.0588235 -15.8227848 -14.2857143 22.8070175\n [25] 3.5714286 3.4482759 18.6666667 -8.4269663 5.5214724 3.4883721\n [31] 11.7977528 0.0000000 -7.5376884 -11.9565217 -9.8765432 13.6986301\n [37] 3.0120482 5.2631579 7.2222222 -6.2176166 1.1049724 19.1256831\n [43] 5.5045872 5.2173913 -13.6363636 -8.6124402 -9.9476440 12.7906977\n [49] 1.0309278 0.0000000 20.4081633 -0.4237288 -2.5531915 6.1135371\n [55] 8.6419753 3.0303030 -12.8676471 -10.9704641 -14.6919431 11.6666667\n [61] 1.4925373 -7.8431373 25.0000000 -3.4042553 3.0837004 12.8205128\n [67] 14.3939394 -2.9801325 -11.6040956 -11.5830116 -11.3537118 12.8078818\n [73] 5.6768559 -3.7190083 14.5922747 0.7490637 0.3717472 16.6666667\n [79] 15.5555556 -4.6703297 -10.0864553 -12.1794872 -13.5036496 17.2995781\n [85] 2.1582734 -2.4647887 14.4404332 -1.2618297 1.5974441 17.6100629\n [91] 10.4278075 -1.9370460 -12.3456790 -13.8028169 -11.4379085 12.9151292\n [97] 2.9411765 -4.4444444 18.2724252 -2.2471910 2.0114943 18.8732394\n[103] 10.1895735 0.4301075 -13.4903640 -14.1089109 -12.1037464 10.1639344\n[109] 1.1904762 -6.4705882 13.8364780 -3.8674033 4.3103448 19.8347107\n[115] 12.8735632 2.8513238 -20.0000000 -11.1386139 -13.6490251 8.7096774\n[121] 6.8249258 -5.0000000 18.7134503 -2.4630542 6.0606061 12.3809524\n[127] 16.1016949 2.0072993 -17.1735242 -12.0950324 -11.0565111 11.8784530\n[133] 2.9629630 -6.2350120 7.1611253 10.0238663 2.3861171 13.3474576\n[139] 16.2616822 -2.5723473 -16.1716172 -9.2519685 -15.4013015 10.7692308\nattr(,\"vector_attributes\")\nattr(,\"vector_attributes\")$tsp\n[1] 1949.000 1960.917 12.000\n\nattr(,\"vector_attributes\")$class\n[1] \"ts\"\n\nattr(,\"name\")\n[1] \"AirPassengers\"\n```\n:::\n:::", + "supporting": [ + "index_files" + ], + "filters": [ + "rmarkdown/pagebreak.lua" + ], + "includes": { + "include-in-header": [ + "\r\n\r\n\r\n\r\n\r\n\r\n\r\n\r\n" + ] + }, + "engineDependencies": {}, + "preserve": {}, + "postProcess": true + } +} \ No newline at end of file diff --git a/_freeze/posts/2011-11-16/index/figure-html/unnamed-chunk-1-1.png b/_freeze/posts/2011-11-16/index/figure-html/unnamed-chunk-1-1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d365804eb63e366243db9f8394baa07c94442b7 GIT binary patch literal 19402 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 GO&ol>RkzEMswnL}xX|Q>55o5-Wsu92BAd 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ǎ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 Hl+XkKW1Z&= literal 0 HcmV?d00001 diff --git a/_freeze/site_libs/htmlwidgets-1.6.2/htmlwidgets.js b/_freeze/site_libs/htmlwidgets-1.6.2/htmlwidgets.js new file mode 100644 index 00000000..1067d029 --- /dev/null +++ b/_freeze/site_libs/htmlwidgets-1.6.2/htmlwidgets.js @@ -0,0 +1,901 @@ +(function() { + // If window.HTMLWidgets is already defined, then use it; otherwise create a + // new object. This allows preceding code to set options that affect the + // initialization process (though none currently exist). + window.HTMLWidgets = window.HTMLWidgets || {}; + + // See if we're running in a viewer pane. If not, we're in a web browser. + var viewerMode = window.HTMLWidgets.viewerMode = + /\bviewer_pane=1\b/.test(window.location); + + // See if we're running in Shiny mode. If not, it's a static document. + // Note that static widgets can appear in both Shiny and static modes, but + // obviously, Shiny widgets can only appear in Shiny apps/documents. + var shinyMode = window.HTMLWidgets.shinyMode = + typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; + + // We can't count on jQuery being available, so we implement our own + // version if necessary. + function querySelectorAll(scope, selector) { + if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) { + return scope.find(selector); + } + if (scope.querySelectorAll) { + return scope.querySelectorAll(selector); + } + } + + function asArray(value) { + if (value === null) + return []; + if ($.isArray(value)) + return value; + return [value]; + } + + // Implement jQuery's extend + function extend(target /*, ... */) { + if (arguments.length == 1) { + return target; + } + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + } + return target; + } + + // IE8 doesn't support Array.forEach. + function forEach(values, callback, thisArg) { + if (values.forEach) { + values.forEach(callback, thisArg); + } else { + for (var i = 0; i < values.length; i++) { + callback.call(thisArg, values[i], i, values); + } + } + } + + // Replaces the specified method with the return value of funcSource. + // + // Note that funcSource should not BE the new method, it should be a function + // that RETURNS the new method. funcSource receives a single argument that is + // the overridden method, it can be called from the new method. The overridden + // method can be called like a regular function, it has the target permanently + // bound to it so "this" will work correctly. + function overrideMethod(target, methodName, funcSource) { + var superFunc = target[methodName] || function() {}; + var superFuncBound = function() { + return superFunc.apply(target, arguments); + }; + target[methodName] = funcSource(superFuncBound); + } + + // Add a method to delegator that, when invoked, calls + // delegatee.methodName. If there is no such method on + // the delegatee, but there was one on delegator before + // delegateMethod was called, then the original version + // is invoked instead. + // For example: + // + // var a = { + // method1: function() { console.log('a1'); } + // method2: function() { console.log('a2'); } + // }; + // var b = { + // method1: function() { console.log('b1'); } + // }; + // delegateMethod(a, b, "method1"); + // delegateMethod(a, b, "method2"); + // a.method1(); + // a.method2(); + // + // The output would be "b1", "a2". + function delegateMethod(delegator, delegatee, methodName) { + var inherited = delegator[methodName]; + delegator[methodName] = function() { + var target = delegatee; + var method = delegatee[methodName]; + + // The method doesn't exist on the delegatee. Instead, + // call the method on the delegator, if it exists. + if (!method) { + target = delegator; + method = inherited; + } + + if (method) { + return method.apply(target, arguments); + } + }; + } + + // Implement a vague facsimilie of jQuery's data method + function elementData(el, name, value) { + if (arguments.length == 2) { + return el["htmlwidget_data_" + name]; + } else if (arguments.length == 3) { + el["htmlwidget_data_" + name] = value; + return el; + } else { + throw new Error("Wrong number of arguments for elementData: " + + arguments.length); + } + } + + // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + } + + function hasClass(el, className) { + var re = new RegExp("\\b" + escapeRegExp(className) + "\\b"); + return re.test(el.className); + } + + // elements - array (or array-like object) of HTML elements + // className - class name to test for + // include - if true, only return elements with given className; + // if false, only return elements *without* given className + function filterByClass(elements, className, include) { + var results = []; + for (var i = 0; i < elements.length; i++) { + if (hasClass(elements[i], className) == include) + results.push(elements[i]); + } + return results; + } + + function on(obj, eventName, func) { + if (obj.addEventListener) { + obj.addEventListener(eventName, func, false); + } else if (obj.attachEvent) { + obj.attachEvent(eventName, func); + } + } + + function off(obj, eventName, func) { + if (obj.removeEventListener) + obj.removeEventListener(eventName, func, false); + else if (obj.detachEvent) { + obj.detachEvent(eventName, func); + } + } + + // Translate array of values to top/right/bottom/left, as usual with + // the "padding" CSS property + // https://developer.mozilla.org/en-US/docs/Web/CSS/padding + function unpackPadding(value) { + if (typeof(value) === "number") + value = [value]; + if (value.length === 1) { + return {top: value[0], right: value[0], bottom: value[0], left: value[0]}; + } + if (value.length === 2) { + return {top: value[0], right: value[1], bottom: value[0], left: value[1]}; + } + if (value.length === 3) { + return {top: value[0], right: value[1], bottom: value[2], left: value[1]}; + } + if (value.length === 4) { + return {top: value[0], right: value[1], bottom: value[2], left: value[3]}; + } + } + + // Convert an unpacked padding object to a CSS value + function paddingToCss(paddingObj) { + return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px"; + } + + // Makes a number suitable for CSS + function px(x) { + if (typeof(x) === "number") + return x + "px"; + else + return x; + } + + // Retrieves runtime widget sizing information for an element. + // The return value is either null, or an object with fill, padding, + // defaultWidth, defaultHeight fields. + function sizingPolicy(el) { + var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']"); + if (!sizingEl) + return null; + var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}"); + if (viewerMode) { + return sp.viewer; + } else { + return sp.browser; + } + } + + // @param tasks Array of strings (or falsy value, in which case no-op). + // Each element must be a valid JavaScript expression that yields a + // function. Or, can be an array of objects with "code" and "data" + // properties; in this case, the "code" property should be a string + // of JS that's an expr that yields a function, and "data" should be + // an object that will be added as an additional argument when that + // function is called. + // @param target The object that will be "this" for each function + // execution. + // @param args Array of arguments to be passed to the functions. (The + // same arguments will be passed to all functions.) + function evalAndRun(tasks, target, args) { + if (tasks) { + forEach(tasks, function(task) { + var theseArgs = args; + if (typeof(task) === "object") { + theseArgs = theseArgs.concat([task.data]); + task = task.code; + } + var taskFunc = tryEval(task); + if (typeof(taskFunc) !== "function") { + throw new Error("Task must be a function! Source:\n" + task); + } + taskFunc.apply(target, theseArgs); + }); + } + } + + // Attempt eval() both with and without enclosing in parentheses. + // Note that enclosing coerces a function declaration into + // an expression that eval() can parse + // (otherwise, a SyntaxError is thrown) + function tryEval(code) { + var result = null; + try { + result = eval("(" + code + ")"); + } catch(error) { + if (!(error instanceof SyntaxError)) { + throw error; + } + try { + result = eval(code); + } catch(e) { + if (e instanceof SyntaxError) { + throw error; + } else { + throw e; + } + } + } + return result; + } + + function initSizing(el) { + var sizing = sizingPolicy(el); + if (!sizing) + return; + + var cel = document.getElementById("htmlwidget_container"); + if (!cel) + return; + + if (typeof(sizing.padding) !== "undefined") { + document.body.style.margin = "0"; + document.body.style.padding = paddingToCss(unpackPadding(sizing.padding)); + } + + if (sizing.fill) { + document.body.style.overflow = "hidden"; + document.body.style.width = "100%"; + document.body.style.height = "100%"; + document.documentElement.style.width = "100%"; + document.documentElement.style.height = "100%"; + cel.style.position = "absolute"; + var pad = unpackPadding(sizing.padding); + cel.style.top = pad.top + "px"; + cel.style.right = pad.right + "px"; + cel.style.bottom = pad.bottom + "px"; + cel.style.left = pad.left + "px"; + el.style.width = "100%"; + el.style.height = "100%"; + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + + } else { + el.style.width = px(sizing.width); + el.style.height = px(sizing.height); + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + } + } + + // Default implementations for methods + var defaults = { + find: function(scope) { + return querySelectorAll(scope, "." + this.name); + }, + renderError: function(el, err) { + var $el = $(el); + + this.clearError(el); + + // Add all these error classes, as Shiny does + var errClass = "shiny-output-error"; + if (err.type !== null) { + // use the classes of the error condition as CSS class names + errClass = errClass + " " + $.map(asArray(err.type), function(type) { + return errClass + "-" + type; + }).join(" "); + } + errClass = errClass + " htmlwidgets-error"; + + // Is el inline or block? If inline or inline-block, just display:none it + // and add an inline error. + var display = $el.css("display"); + $el.data("restore-display-mode", display); + + if (display === "inline" || display === "inline-block") { + $el.hide(); + if (err.message !== "") { + var errorSpan = $("").addClass(errClass); + errorSpan.text(err.message); + $el.after(errorSpan); + } + } else if (display === "block") { + // If block, add an error just after the el, set visibility:none on the + // el, and position the error to be on top of the el. + // Mark it with a unique ID and CSS class so we can remove it later. + $el.css("visibility", "hidden"); + if (err.message !== "") { + var errorDiv = $("
").addClass(errClass).css("position", "absolute") + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + // setting width can push out the page size, forcing otherwise + // unnecessary scrollbars to appear and making it impossible for + // the element to shrink; so use max-width instead + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + errorDiv.text(err.message); + $el.after(errorDiv); + + // Really dumb way to keep the size/position of the error in sync with + // the parent element as the window is resized or whatever. + var intId = setInterval(function() { + if (!errorDiv[0].parentElement) { + clearInterval(intId); + return; + } + errorDiv + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + }, 500); + } + } + }, + clearError: function(el) { + var $el = $(el); + var display = $el.data("restore-display-mode"); + $el.data("restore-display-mode", null); + + if (display === "inline" || display === "inline-block") { + if (display) + $el.css("display", display); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } else if (display === "block"){ + $el.css("visibility", "inherit"); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } + }, + sizing: {} + }; + + // Called by widget bindings to register a new type of widget. The definition + // object can contain the following properties: + // - name (required) - A string indicating the binding name, which will be + // used by default as the CSS classname to look for. + // - initialize (optional) - A function(el) that will be called once per + // widget element; if a value is returned, it will be passed as the third + // value to renderValue. + // - renderValue (required) - A function(el, data, initValue) that will be + // called with data. Static contexts will cause this to be called once per + // element; Shiny apps will cause this to be called multiple times per + // element, as the data changes. + window.HTMLWidgets.widget = function(definition) { + if (!definition.name) { + throw new Error("Widget must have a name"); + } + if (!definition.type) { + throw new Error("Widget must have a type"); + } + // Currently we only support output widgets + if (definition.type !== "output") { + throw new Error("Unrecognized widget type '" + definition.type + "'"); + } + // TODO: Verify that .name is a valid CSS classname + + // Support new-style instance-bound definitions. Old-style class-bound + // definitions have one widget "object" per widget per type/class of + // widget; the renderValue and resize methods on such widget objects + // take el and instance arguments, because the widget object can't + // store them. New-style instance-bound definitions have one widget + // object per widget instance; the definition that's passed in doesn't + // provide renderValue or resize methods at all, just the single method + // factory(el, width, height) + // which returns an object that has renderValue(x) and resize(w, h). + // This enables a far more natural programming style for the widget + // author, who can store per-instance state using either OO-style + // instance fields or functional-style closure variables (I guess this + // is in contrast to what can only be called C-style pseudo-OO which is + // what we required before). + if (definition.factory) { + definition = createLegacyDefinitionAdapter(definition); + } + + if (!definition.renderValue) { + throw new Error("Widget must have a renderValue function"); + } + + // For static rendering (non-Shiny), use a simple widget registration + // scheme. We also use this scheme for Shiny apps/documents that also + // contain static widgets. + window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; + // Merge defaults into the definition; don't mutate the original definition. + var staticBinding = extend({}, defaults, definition); + overrideMethod(staticBinding, "find", function(superfunc) { + return function(scope) { + var results = superfunc(scope); + // Filter out Shiny outputs, we only want the static kind + return filterByClass(results, "html-widget-output", false); + }; + }); + window.HTMLWidgets.widgets.push(staticBinding); + + if (shinyMode) { + // Shiny is running. Register the definition with an output binding. + // The definition itself will not be the output binding, instead + // we will make an output binding object that delegates to the + // definition. This is because we foolishly used the same method + // name (renderValue) for htmlwidgets definition and Shiny bindings + // but they actually have quite different semantics (the Shiny + // bindings receive data that includes lots of metadata that it + // strips off before calling htmlwidgets renderValue). We can't + // just ignore the difference because in some widgets it's helpful + // to call this.renderValue() from inside of resize(), and if + // we're not delegating, then that call will go to the Shiny + // version instead of the htmlwidgets version. + + // Merge defaults with definition, without mutating either. + var bindingDef = extend({}, defaults, definition); + + // This object will be our actual Shiny binding. + var shinyBinding = new Shiny.OutputBinding(); + + // With a few exceptions, we'll want to simply use the bindingDef's + // version of methods if they are available, otherwise fall back to + // Shiny's defaults. NOTE: If Shiny's output bindings gain additional + // methods in the future, and we want them to be overrideable by + // HTMLWidget binding definitions, then we'll need to add them to this + // list. + delegateMethod(shinyBinding, bindingDef, "getId"); + delegateMethod(shinyBinding, bindingDef, "onValueChange"); + delegateMethod(shinyBinding, bindingDef, "onValueError"); + delegateMethod(shinyBinding, bindingDef, "renderError"); + delegateMethod(shinyBinding, bindingDef, "clearError"); + delegateMethod(shinyBinding, bindingDef, "showProgress"); + + // The find, renderValue, and resize are handled differently, because we + // want to actually decorate the behavior of the bindingDef methods. + + shinyBinding.find = function(scope) { + var results = bindingDef.find(scope); + + // Only return elements that are Shiny outputs, not static ones + var dynamicResults = results.filter(".html-widget-output"); + + // It's possible that whatever caused Shiny to think there might be + // new dynamic outputs, also caused there to be new static outputs. + // Since there might be lots of different htmlwidgets bindings, we + // schedule execution for later--no need to staticRender multiple + // times. + if (results.length !== dynamicResults.length) + scheduleStaticRender(); + + return dynamicResults; + }; + + // Wrap renderValue to handle initialization, which unfortunately isn't + // supported natively by Shiny at the time of this writing. + + shinyBinding.renderValue = function(el, data) { + Shiny.renderDependencies(data.deps); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var i = 0; data.evals && i < data.evals.length; i++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); + } + if (!bindingDef.renderOnNullValue) { + if (data.x === null) { + el.style.visibility = "hidden"; + return; + } else { + el.style.visibility = "inherit"; + } + } + if (!elementData(el, "initialized")) { + initSizing(el); + + elementData(el, "initialized", true); + if (bindingDef.initialize) { + var rect = el.getBoundingClientRect(); + var result = bindingDef.initialize(el, rect.width, rect.height); + elementData(el, "init_result", result); + } + } + bindingDef.renderValue(el, data.x, elementData(el, "init_result")); + evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); + }; + + // Only override resize if bindingDef implements it + if (bindingDef.resize) { + shinyBinding.resize = function(el, width, height) { + // Shiny can call resize before initialize/renderValue have been + // called, which doesn't make sense for widgets. + if (elementData(el, "initialized")) { + bindingDef.resize(el, width, height, elementData(el, "init_result")); + } + }; + } + + Shiny.outputBindings.register(shinyBinding, bindingDef.name); + } + }; + + var scheduleStaticRenderTimerId = null; + function scheduleStaticRender() { + if (!scheduleStaticRenderTimerId) { + scheduleStaticRenderTimerId = setTimeout(function() { + scheduleStaticRenderTimerId = null; + window.HTMLWidgets.staticRender(); + }, 1); + } + } + + // Render static widgets after the document finishes loading + // Statically render all elements that are of this widget's class + window.HTMLWidgets.staticRender = function() { + var bindings = window.HTMLWidgets.widgets || []; + forEach(bindings, function(binding) { + var matches = binding.find(document.documentElement); + forEach(matches, function(el) { + var sizeObj = initSizing(el, binding); + + var getSize = function(el) { + if (sizeObj) { + return {w: sizeObj.getWidth(), h: sizeObj.getHeight()} + } else { + var rect = el.getBoundingClientRect(); + return {w: rect.width, h: rect.height} + } + }; + + if (hasClass(el, "html-widget-static-bound")) + return; + el.className = el.className + " html-widget-static-bound"; + + var initResult; + if (binding.initialize) { + var size = getSize(el); + initResult = binding.initialize(el, size.w, size.h); + elementData(el, "init_result", initResult); + } + + if (binding.resize) { + var lastSize = getSize(el); + var resizeHandler = function(e) { + var size = getSize(el); + if (size.w === 0 && size.h === 0) + return; + if (size.w === lastSize.w && size.h === lastSize.h) + return; + lastSize = size; + binding.resize(el, size.w, size.h, initResult); + }; + + on(window, "resize", resizeHandler); + + // This is needed for cases where we're running in a Shiny + // app, but the widget itself is not a Shiny output, but + // rather a simple static widget. One example of this is + // an rmarkdown document that has runtime:shiny and widget + // that isn't in a render function. Shiny only knows to + // call resize handlers for Shiny outputs, not for static + // widgets, so we do it ourselves. + if (window.jQuery) { + window.jQuery(document).on( + "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", + resizeHandler + ); + window.jQuery(document).on( + "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", + resizeHandler + ); + } + + // This is needed for the specific case of ioslides, which + // flips slides between display:none and display:block. + // Ideally we would not have to have ioslide-specific code + // here, but rather have ioslides raise a generic event, + // but the rmarkdown package just went to CRAN so the + // window to getting that fixed may be long. + if (window.addEventListener) { + // It's OK to limit this to window.addEventListener + // browsers because ioslides itself only supports + // such browsers. + on(document, "slideenter", resizeHandler); + on(document, "slideleave", resizeHandler); + } + } + + var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); + if (scriptData) { + var data = JSON.parse(scriptData.textContent || scriptData.text); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var k = 0; data.evals && k < data.evals.length; k++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); + } + binding.renderValue(el, data.x, initResult); + evalAndRun(data.jsHooks.render, initResult, [el, data.x]); + } + }); + }); + + invokePostRenderHandlers(); + } + + + function has_jQuery3() { + if (!window.jQuery) { + return false; + } + var $version = window.jQuery.fn.jquery; + var $major_version = parseInt($version.split(".")[0]); + return $major_version >= 3; + } + + /* + / Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's + / on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now + / really means $(setTimeout(fn)). + / https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous + / + / Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny + / one tick later than it did before, which means staticRender() is + / called renderValue() earlier than (advanced) widget authors might be expecting. + / https://github.com/rstudio/shiny/issues/2630 + / + / For a concrete example, leaflet has some methods (e.g., updateBounds) + / which reference Shiny methods registered in initShiny (e.g., setInputValue). + / Since leaflet is privy to this life-cycle, it knows to use setTimeout() to + / delay execution of those methods (until Shiny methods are ready) + / https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268 + / + / Ideally widget authors wouldn't need to use this setTimeout() hack that + / leaflet uses to call Shiny methods on a staticRender(). In the long run, + / the logic initShiny should be broken up so that method registration happens + / right away, but binding happens later. + */ + function maybeStaticRenderLater() { + if (shinyMode && has_jQuery3()) { + window.jQuery(window.HTMLWidgets.staticRender); + } else { + window.HTMLWidgets.staticRender(); + } + } + + if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", function() { + document.removeEventListener("DOMContentLoaded", arguments.callee, false); + maybeStaticRenderLater(); + }, false); + } else if (document.attachEvent) { + document.attachEvent("onreadystatechange", function() { + if (document.readyState === "complete") { + document.detachEvent("onreadystatechange", arguments.callee); + maybeStaticRenderLater(); + } + }); + } + + + window.HTMLWidgets.getAttachmentUrl = function(depname, key) { + // If no key, default to the first item + if (typeof(key) === "undefined") + key = 1; + + var link = document.getElementById(depname + "-" + key + "-attachment"); + if (!link) { + throw new Error("Attachment " + depname + "/" + key + " not found in document"); + } + return link.getAttribute("href"); + }; + + window.HTMLWidgets.dataframeToD3 = function(df) { + var names = []; + var length; + for (var name in df) { + if (df.hasOwnProperty(name)) + names.push(name); + if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { + throw new Error("All fields must be arrays"); + } else if (typeof(length) !== "undefined" && length !== df[name].length) { + throw new Error("All fields must be arrays of the same length"); + } + length = df[name].length; + } + var results = []; + var item; + for (var row = 0; row < length; row++) { + item = {}; + for (var col = 0; col < names.length; col++) { + item[names[col]] = df[names[col]][row]; + } + results.push(item); + } + return results; + }; + + window.HTMLWidgets.transposeArray2D = function(array) { + if (array.length === 0) return array; + var newArray = array[0].map(function(col, i) { + return array.map(function(row) { + return row[i] + }) + }); + return newArray; + }; + // Split value at splitChar, but allow splitChar to be escaped + // using escapeChar. Any other characters escaped by escapeChar + // will be included as usual (including escapeChar itself). + function splitWithEscape(value, splitChar, escapeChar) { + var results = []; + var escapeMode = false; + var currentResult = ""; + for (var pos = 0; pos < value.length; pos++) { + if (!escapeMode) { + if (value[pos] === splitChar) { + results.push(currentResult); + currentResult = ""; + } else if (value[pos] === escapeChar) { + escapeMode = true; + } else { + currentResult += value[pos]; + } + } else { + currentResult += value[pos]; + escapeMode = false; + } + } + if (currentResult !== "") { + results.push(currentResult); + } + return results; + } + // Function authored by Yihui/JJ Allaire + window.HTMLWidgets.evaluateStringMember = function(o, member) { + var parts = splitWithEscape(member, '.', '\\'); + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i]; + // part may be a character or 'numeric' member name + if (o !== null && typeof o === "object" && part in o) { + if (i == (l - 1)) { // if we are at the end of the line then evalulate + if (typeof o[part] === "string") + o[part] = tryEval(o[part]); + } else { // otherwise continue to next embedded object + o = o[part]; + } + } + } + }; + + // Retrieve the HTMLWidget instance (i.e. the return value of an + // HTMLWidget binding's initialize() or factory() function) + // associated with an element, or null if none. + window.HTMLWidgets.getInstance = function(el) { + return elementData(el, "init_result"); + }; + + // Finds the first element in the scope that matches the selector, + // and returns the HTMLWidget instance (i.e. the return value of + // an HTMLWidget binding's initialize() or factory() function) + // associated with that element, if any. If no element matches the + // selector, or the first matching element has no HTMLWidget + // instance associated with it, then null is returned. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.find = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var el = scope.querySelector(selector); + if (el === null) { + return null; + } else { + return window.HTMLWidgets.getInstance(el); + } + }; + + // Finds all elements in the scope that match the selector, and + // returns the HTMLWidget instances (i.e. the return values of + // an HTMLWidget binding's initialize() or factory() function) + // associated with the elements, in an array. If elements that + // match the selector don't have an associated HTMLWidget + // instance, the returned array will contain nulls. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.findAll = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var nodes = scope.querySelectorAll(selector); + var results = []; + for (var i = 0; i < nodes.length; i++) { + results.push(window.HTMLWidgets.getInstance(nodes[i])); + } + return results; + }; + + var postRenderHandlers = []; + function invokePostRenderHandlers() { + while (postRenderHandlers.length) { + var handler = postRenderHandlers.shift(); + if (handler) { + handler(); + } + } + } + + // Register the given callback function to be invoked after the + // next time static widgets are rendered. + window.HTMLWidgets.addPostRenderHandler = function(callback) { + postRenderHandlers.push(callback); + }; + + // Takes a new-style instance-bound definition, and returns an + // old-style class-bound definition. This saves us from having + // to rewrite all the logic in this file to accomodate both + // types of definitions. + function createLegacyDefinitionAdapter(defn) { + var result = { + name: defn.name, + type: defn.type, + initialize: function(el, width, height) { + return defn.factory(el, width, height); + }, + renderValue: function(el, x, instance) { + return instance.renderValue(x); + }, + resize: function(el, width, height, instance) { + return instance.resize(width, height); + } + }; + + if (defn.find) + result.find = defn.find; + if (defn.renderError) + result.renderError = defn.renderError; + if (defn.clearError) + result.clearError = defn.clearError; + + return result; + } +})(); diff --git a/_freeze/site_libs/plotly-binding-4.10.3/plotly.js b/_freeze/site_libs/plotly-binding-4.10.3/plotly.js new file mode 100644 index 00000000..7a2a143b --- /dev/null +++ b/_freeze/site_libs/plotly-binding-4.10.3/plotly.js @@ -0,0 +1,941 @@ + +HTMLWidgets.widget({ + name: "plotly", + type: "output", + + initialize: function(el, width, height) { + return {}; + }, + + resize: function(el, width, height, instance) { + if (instance.autosize) { + var width = instance.width || width; + var height = instance.height || height; + Plotly.relayout(el.id, {width: width, height: height}); + } + }, + + renderValue: function(el, x, instance) { + + // Plotly.relayout() mutates the plot input object, so make sure to + // keep a reference to the user-supplied width/height *before* + // we call Plotly.plot(); + var lay = x.layout || {}; + instance.width = lay.width; + instance.height = lay.height; + instance.autosize = lay.autosize || true; + + /* + / 'inform the world' about highlighting options this is so other + / crosstalk libraries have a chance to respond to special settings + / such as persistent selection. + / AFAIK, leaflet is the only library with such intergration + / https://github.com/rstudio/leaflet/pull/346/files#diff-ad0c2d51ce5fdf8c90c7395b102f4265R154 + */ + var ctConfig = crosstalk.var('plotlyCrosstalkOpts').set(x.highlight); + + if (typeof(window) !== "undefined") { + // make sure plots don't get created outside the network (for on-prem) + window.PLOTLYENV = window.PLOTLYENV || {}; + window.PLOTLYENV.BASE_URL = x.base_url; + + // Enable persistent selection when shift key is down + // https://stackoverflow.com/questions/1828613/check-if-a-key-is-down + var persistOnShift = function(e) { + if (!e) window.event; + if (e.shiftKey) { + x.highlight.persistent = true; + x.highlight.persistentShift = true; + } else { + x.highlight.persistent = false; + x.highlight.persistentShift = false; + } + }; + + // Only relevant if we haven't forced persistent mode at command line + if (!x.highlight.persistent) { + window.onmousemove = persistOnShift; + } + } + + var graphDiv = document.getElementById(el.id); + + // TODO: move the control panel injection strategy inside here... + HTMLWidgets.addPostRenderHandler(function() { + + // lower the z-index of the modebar to prevent it from highjacking hover + // (TODO: do this via CSS?) + // https://github.com/ropensci/plotly/issues/956 + // https://www.w3schools.com/jsref/prop_style_zindex.asp + var modebars = document.querySelectorAll(".js-plotly-plot .plotly .modebar"); + for (var i = 0; i < modebars.length; i++) { + modebars[i].style.zIndex = 1; + } + }); + + // inject a "control panel" holding selectize/dynamic color widget(s) + if ((x.selectize || x.highlight.dynamic) && !instance.plotly) { + var flex = document.createElement("div"); + flex.class = "plotly-crosstalk-control-panel"; + flex.style = "display: flex; flex-wrap: wrap"; + + // inject the colourpicker HTML container into the flexbox + if (x.highlight.dynamic) { + var pickerDiv = document.createElement("div"); + + var pickerInput = document.createElement("input"); + pickerInput.id = el.id + "-colourpicker"; + pickerInput.placeholder = "asdasd"; + + var pickerLabel = document.createElement("label"); + pickerLabel.for = pickerInput.id; + pickerLabel.innerHTML = "Brush color  "; + + pickerDiv.appendChild(pickerLabel); + pickerDiv.appendChild(pickerInput); + flex.appendChild(pickerDiv); + } + + // inject selectize HTML containers (one for every crosstalk group) + if (x.selectize) { + var ids = Object.keys(x.selectize); + + for (var i = 0; i < ids.length; i++) { + var container = document.createElement("div"); + container.id = ids[i]; + container.style = "width: 80%; height: 10%"; + container.class = "form-group crosstalk-input-plotly-highlight"; + + var label = document.createElement("label"); + label.for = ids[i]; + label.innerHTML = x.selectize[ids[i]].group; + label.class = "control-label"; + + var selectDiv = document.createElement("div"); + var select = document.createElement("select"); + select.multiple = true; + + selectDiv.appendChild(select); + container.appendChild(label); + container.appendChild(selectDiv); + flex.appendChild(container); + } + } + + // finally, insert the flexbox inside the htmlwidget container, + // but before the plotly graph div + graphDiv.parentElement.insertBefore(flex, graphDiv); + + if (x.highlight.dynamic) { + var picker = $("#" + pickerInput.id); + var colors = x.highlight.color || []; + // TODO: let users specify options? + var opts = { + value: colors[0], + showColour: "both", + palette: "limited", + allowedCols: colors.join(" "), + width: "20%", + height: "10%" + }; + picker.colourpicker({changeDelay: 0}); + picker.colourpicker("settings", opts); + picker.colourpicker("value", opts.value); + // inform crosstalk about a change in the current selection colour + var grps = x.highlight.ctGroups || []; + for (var i = 0; i < grps.length; i++) { + crosstalk.group(grps[i]).var('plotlySelectionColour') + .set(picker.colourpicker('value')); + } + picker.on("change", function() { + for (var i = 0; i < grps.length; i++) { + crosstalk.group(grps[i]).var('plotlySelectionColour') + .set(picker.colourpicker('value')); + } + }); + } + } + + // if no plot exists yet, create one with a particular configuration + if (!instance.plotly) { + + var plot = Plotly.newPlot(graphDiv, x); + instance.plotly = true; + + } else if (x.layout.transition) { + + var plot = Plotly.react(graphDiv, x); + + } else { + + // this is essentially equivalent to Plotly.newPlot(), but avoids creating + // a new webgl context + // https://github.com/plotly/plotly.js/blob/2b24f9def901831e61282076cf3f835598d56f0e/src/plot_api/plot_api.js#L531-L532 + + // TODO: restore crosstalk selections? + Plotly.purge(graphDiv); + // TODO: why is this necessary to get crosstalk working? + graphDiv.data = undefined; + graphDiv.layout = undefined; + var plot = Plotly.newPlot(graphDiv, x); + } + + // Trigger plotly.js calls defined via `plotlyProxy()` + plot.then(function() { + if (HTMLWidgets.shinyMode) { + Shiny.addCustomMessageHandler("plotly-calls", function(msg) { + var gd = document.getElementById(msg.id); + if (!gd) { + throw new Error("Couldn't find plotly graph with id: " + msg.id); + } + // This isn't an official plotly.js method, but it's the only current way to + // change just the configuration of a plot + // https://community.plot.ly/t/update-config-function/9057 + if (msg.method == "reconfig") { + Plotly.react(gd, gd.data, gd.layout, msg.args); + return; + } + if (!Plotly[msg.method]) { + throw new Error("Unknown method " + msg.method); + } + var args = [gd].concat(msg.args); + Plotly[msg.method].apply(null, args); + }); + } + + // plotly's mapbox API doesn't currently support setting bounding boxes + // https://www.mapbox.com/mapbox-gl-js/example/fitbounds/ + // so we do this manually... + // TODO: make sure this triggers on a redraw and relayout as well as on initial draw + var mapboxIDs = graphDiv._fullLayout._subplots.mapbox || []; + for (var i = 0; i < mapboxIDs.length; i++) { + var id = mapboxIDs[i]; + var mapOpts = x.layout[id] || {}; + var args = mapOpts._fitBounds || {}; + if (!args) { + continue; + } + var mapObj = graphDiv._fullLayout[id]._subplot.map; + mapObj.fitBounds(args.bounds, args.options); + } + + }); + + // Attach attributes (e.g., "key", "z") to plotly event data + function eventDataWithKey(eventData) { + if (eventData === undefined || !eventData.hasOwnProperty("points")) { + return null; + } + return eventData.points.map(function(pt) { + var obj = { + curveNumber: pt.curveNumber, + pointNumber: pt.pointNumber, + x: pt.x, + y: pt.y + }; + + // If 'z' is reported with the event data, then use it! + if (pt.hasOwnProperty("z")) { + obj.z = pt.z; + } + + if (pt.hasOwnProperty("customdata")) { + obj.customdata = pt.customdata; + } + + /* + TL;DR: (I think) we have to select the graph div (again) to attach keys... + + Why? Remember that crosstalk will dynamically add/delete traces + (see traceManager.prototype.updateSelection() below) + For this reason, we can't simply grab keys from x.data (like we did previously) + Moreover, we can't use _fullData, since that doesn't include + unofficial attributes. It's true that click/hover events fire with + pt.data, but drag events don't... + */ + var gd = document.getElementById(el.id); + var trace = gd.data[pt.curveNumber]; + + if (!trace._isSimpleKey) { + var attrsToAttach = ["key"]; + } else { + // simple keys fire the whole key + obj.key = trace.key; + var attrsToAttach = []; + } + + for (var i = 0; i < attrsToAttach.length; i++) { + var attr = trace[attrsToAttach[i]]; + if (Array.isArray(attr)) { + if (typeof pt.pointNumber === "number") { + obj[attrsToAttach[i]] = attr[pt.pointNumber]; + } else if (Array.isArray(pt.pointNumber)) { + obj[attrsToAttach[i]] = attr[pt.pointNumber[0]][pt.pointNumber[1]]; + } else if (Array.isArray(pt.pointNumbers)) { + obj[attrsToAttach[i]] = pt.pointNumbers.map(function(idx) { return attr[idx]; }); + } + } + } + return obj; + }); + } + + + var legendEventData = function(d) { + // if legendgroup is not relevant just return the trace + var trace = d.data[d.curveNumber]; + if (!trace.legendgroup) return trace; + + // if legendgroup was specified, return all traces that match the group + var legendgrps = d.data.map(function(trace){ return trace.legendgroup; }); + var traces = []; + for (i = 0; i < legendgrps.length; i++) { + if (legendgrps[i] == trace.legendgroup) { + traces.push(d.data[i]); + } + } + + return traces; + }; + + + // send user input event data to shiny + if (HTMLWidgets.shinyMode && Shiny.setInputValue) { + + // Some events clear other input values + // TODO: always register these? + var eventClearMap = { + plotly_deselect: ["plotly_selected", "plotly_selecting", "plotly_brushed", "plotly_brushing", "plotly_click"], + plotly_unhover: ["plotly_hover"], + plotly_doubleclick: ["plotly_click"] + }; + + Object.keys(eventClearMap).map(function(evt) { + graphDiv.on(evt, function() { + var inputsToClear = eventClearMap[evt]; + inputsToClear.map(function(input) { + Shiny.setInputValue(input + "-" + x.source, null, {priority: "event"}); + }); + }); + }); + + var eventDataFunctionMap = { + plotly_click: eventDataWithKey, + plotly_sunburstclick: eventDataWithKey, + plotly_hover: eventDataWithKey, + plotly_unhover: eventDataWithKey, + // If 'plotly_selected' has already been fired, and you click + // on the plot afterwards, this event fires `undefined`?!? + // That might be considered a plotly.js bug, but it doesn't make + // sense for this input change to occur if `d` is falsy because, + // even in the empty selection case, `d` is truthy (an object), + // and the 'plotly_deselect' event will reset this input + plotly_selected: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_selecting: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_brushed: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_brushing: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_legendclick: legendEventData, + plotly_legenddoubleclick: legendEventData, + plotly_clickannotation: function(d) { return d.fullAnnotation } + }; + + var registerShinyValue = function(event) { + var eventDataPreProcessor = eventDataFunctionMap[event] || function(d) { return d ? d : el.id }; + // some events are unique to the R package + var plotlyJSevent = (event == "plotly_brushed") ? "plotly_selected" : (event == "plotly_brushing") ? "plotly_selecting" : event; + // register the event + graphDiv.on(plotlyJSevent, function(d) { + Shiny.setInputValue( + event + "-" + x.source, + JSON.stringify(eventDataPreProcessor(d)), + {priority: "event"} + ); + }); + } + + var shinyEvents = x.shinyEvents || []; + shinyEvents.map(registerShinyValue); + } + + // Given an array of {curveNumber: x, pointNumber: y} objects, + // return a hash of { + // set1: {value: [key1, key2, ...], _isSimpleKey: false}, + // set2: {value: [key3, key4, ...], _isSimpleKey: false} + // } + function pointsToKeys(points) { + var keysBySet = {}; + for (var i = 0; i < points.length; i++) { + + var trace = graphDiv.data[points[i].curveNumber]; + if (!trace.key || !trace.set) { + continue; + } + + // set defaults for this keySet + // note that we don't track the nested property (yet) since we always + // emit the union -- http://cpsievert.github.io/talks/20161212b/#21 + keysBySet[trace.set] = keysBySet[trace.set] || { + value: [], + _isSimpleKey: trace._isSimpleKey + }; + + // Use pointNumber by default, but aggregated traces should emit pointNumbers + var ptNum = points[i].pointNumber; + var hasPtNum = typeof ptNum === "number"; + var ptNum = hasPtNum ? ptNum : points[i].pointNumbers; + + // selecting a point of a "simple" trace means: select the + // entire key attached to this trace, which is useful for, + // say clicking on a fitted line to select corresponding observations + var key = trace._isSimpleKey ? trace.key : Array.isArray(ptNum) ? ptNum.map(function(idx) { return trace.key[idx]; }) : trace.key[ptNum]; + // http://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays-in-javascript + var keyFlat = trace._isNestedKey ? [].concat.apply([], key) : key; + + // TODO: better to only add new values? + keysBySet[trace.set].value = keysBySet[trace.set].value.concat(keyFlat); + } + + return keysBySet; + } + + + x.highlight.color = x.highlight.color || []; + // make sure highlight color is an array + if (!Array.isArray(x.highlight.color)) { + x.highlight.color = [x.highlight.color]; + } + + var traceManager = new TraceManager(graphDiv, x.highlight); + + // Gather all *unique* sets. + var allSets = []; + for (var curveIdx = 0; curveIdx < x.data.length; curveIdx++) { + var newSet = x.data[curveIdx].set; + if (newSet) { + if (allSets.indexOf(newSet) === -1) { + allSets.push(newSet); + } + } + } + + // register event listeners for all sets + for (var i = 0; i < allSets.length; i++) { + + var set = allSets[i]; + var selection = new crosstalk.SelectionHandle(set); + var filter = new crosstalk.FilterHandle(set); + + var filterChange = function(e) { + removeBrush(el); + traceManager.updateFilter(set, e.value); + }; + filter.on("change", filterChange); + + + var selectionChange = function(e) { + + // Workaround for 'plotly_selected' now firing previously selected + // points (in addition to new ones) when holding shift key. In our case, + // we just want the new keys + if (x.highlight.on === "plotly_selected" && x.highlight.persistentShift) { + // https://stackoverflow.com/questions/1187518/how-to-get-the-difference-between-two-arrays-in-javascript + Array.prototype.diff = function(a) { + return this.filter(function(i) {return a.indexOf(i) < 0;}); + }; + e.value = e.value.diff(e.oldValue); + } + + // array of "event objects" tracking the selection history + // this is used to avoid adding redundant selections + var selectionHistory = crosstalk.var("plotlySelectionHistory").get() || []; + + // Construct an event object "defining" the current event. + var event = { + receiverID: traceManager.gd.id, + plotlySelectionColour: crosstalk.group(set).var("plotlySelectionColour").get() + }; + event[set] = e.value; + // TODO: is there a smarter way to check object equality? + if (selectionHistory.length > 0) { + var ev = JSON.stringify(event); + for (var i = 0; i < selectionHistory.length; i++) { + var sel = JSON.stringify(selectionHistory[i]); + if (sel == ev) { + return; + } + } + } + + // accumulate history for persistent selection + if (!x.highlight.persistent) { + selectionHistory = [event]; + } else { + selectionHistory.push(event); + } + crosstalk.var("plotlySelectionHistory").set(selectionHistory); + + // do the actual updating of traces, frames, and the selectize widget + traceManager.updateSelection(set, e.value); + // https://github.com/selectize/selectize.js/blob/master/docs/api.md#methods_items + if (x.selectize) { + if (!x.highlight.persistent || e.value === null) { + selectize.clear(true); + } + selectize.addItems(e.value, true); + selectize.close(); + } + } + selection.on("change", selectionChange); + + // Set a crosstalk variable selection value, triggering an update + var turnOn = function(e) { + if (e) { + var selectedKeys = pointsToKeys(e.points); + // Keys are group names, values are array of selected keys from group. + for (var set in selectedKeys) { + if (selectedKeys.hasOwnProperty(set)) { + selection.set(selectedKeys[set].value, {sender: el}); + } + } + } + }; + if (x.highlight.debounce > 0) { + turnOn = debounce(turnOn, x.highlight.debounce); + } + graphDiv.on(x.highlight.on, turnOn); + + graphDiv.on(x.highlight.off, function turnOff(e) { + // remove any visual clues + removeBrush(el); + // remove any selection history + crosstalk.var("plotlySelectionHistory").set(null); + // trigger the actual removal of selection traces + selection.set(null, {sender: el}); + }); + + // register a callback for selectize so that there is bi-directional + // communication between the widget and direct manipulation events + if (x.selectize) { + var selectizeID = Object.keys(x.selectize)[i]; + var options = x.selectize[selectizeID]; + var first = [{value: "", label: "(All)"}]; + var opts = $.extend({ + options: first.concat(options.items), + searchField: "label", + valueField: "value", + labelField: "label", + maxItems: 50 + }, + options + ); + var select = $("#" + selectizeID).find("select")[0]; + var selectize = $(select).selectize(opts)[0].selectize; + // NOTE: this callback is triggered when *directly* altering + // dropdown items + selectize.on("change", function() { + var currentItems = traceManager.groupSelections[set] || []; + if (!x.highlight.persistent) { + removeBrush(el); + for (var i = 0; i < currentItems.length; i++) { + selectize.removeItem(currentItems[i], true); + } + } + var newItems = selectize.items.filter(function(idx) { + return currentItems.indexOf(idx) < 0; + }); + if (newItems.length > 0) { + traceManager.updateSelection(set, newItems); + } else { + // Item has been removed... + // TODO: this logic won't work for dynamically changing palette + traceManager.updateSelection(set, null); + traceManager.updateSelection(set, selectize.items); + } + }); + } + } // end of selectionChange + + } // end of renderValue +}); // end of widget definition + +/** + * @param graphDiv The Plotly graph div + * @param highlight An object with options for updating selection(s) + */ +function TraceManager(graphDiv, highlight) { + // The Plotly graph div + this.gd = graphDiv; + + // Preserve the original data. + // TODO: try using Lib.extendFlat() as done in + // https://github.com/plotly/plotly.js/pull/1136 + this.origData = JSON.parse(JSON.stringify(graphDiv.data)); + + // avoid doing this over and over + this.origOpacity = []; + for (var i = 0; i < this.origData.length; i++) { + this.origOpacity[i] = this.origData[i].opacity === 0 ? 0 : (this.origData[i].opacity || 1); + } + + // key: group name, value: null or array of keys representing the + // most recently received selection for that group. + this.groupSelections = {}; + + // selection parameters (e.g., transient versus persistent selection) + this.highlight = highlight; +} + +TraceManager.prototype.close = function() { + // TODO: Unhook all event handlers +}; + +TraceManager.prototype.updateFilter = function(group, keys) { + + if (typeof(keys) === "undefined" || keys === null) { + + this.gd.data = JSON.parse(JSON.stringify(this.origData)); + + } else { + + var traces = []; + for (var i = 0; i < this.origData.length; i++) { + var trace = this.origData[i]; + if (!trace.key || trace.set !== group) { + continue; + } + var matchFunc = getMatchFunc(trace); + var matches = matchFunc(trace.key, keys); + + if (matches.length > 0) { + if (!trace._isSimpleKey) { + // subsetArrayAttrs doesn't mutate trace (it makes a modified clone) + trace = subsetArrayAttrs(trace, matches); + } + traces.push(trace); + } + } + this.gd.data = traces; + } + + Plotly.redraw(this.gd); + + // NOTE: we purposely do _not_ restore selection(s), since on filter, + // axis likely will update, changing the pixel -> data mapping, leading + // to a likely mismatch in the brush outline and highlighted marks + +}; + +TraceManager.prototype.updateSelection = function(group, keys) { + + if (keys !== null && !Array.isArray(keys)) { + throw new Error("Invalid keys argument; null or array expected"); + } + + // if selection has been cleared, or if this is transient + // selection, delete the "selection traces" + var nNewTraces = this.gd.data.length - this.origData.length; + if (keys === null || !this.highlight.persistent && nNewTraces > 0) { + var tracesToRemove = []; + for (var i = 0; i < this.gd.data.length; i++) { + if (this.gd.data[i]._isCrosstalkTrace) tracesToRemove.push(i); + } + Plotly.deleteTraces(this.gd, tracesToRemove); + this.groupSelections[group] = keys; + } else { + // add to the groupSelection, rather than overwriting it + // TODO: can this be removed? + this.groupSelections[group] = this.groupSelections[group] || []; + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + if (this.groupSelections[group].indexOf(k) < 0) { + this.groupSelections[group].push(k); + } + } + } + + if (keys === null) { + + Plotly.restyle(this.gd, {"opacity": this.origOpacity}); + + } else if (keys.length >= 1) { + + // placeholder for new "selection traces" + var traces = []; + // this variable is set in R/highlight.R + var selectionColour = crosstalk.group(group).var("plotlySelectionColour").get() || + this.highlight.color[0]; + + for (var i = 0; i < this.origData.length; i++) { + // TODO: try using Lib.extendFlat() as done in + // https://github.com/plotly/plotly.js/pull/1136 + var trace = JSON.parse(JSON.stringify(this.gd.data[i])); + if (!trace.key || trace.set !== group) { + continue; + } + // Get sorted array of matching indices in trace.key + var matchFunc = getMatchFunc(trace); + var matches = matchFunc(trace.key, keys); + + if (matches.length > 0) { + // If this is a "simple" key, that means select the entire trace + if (!trace._isSimpleKey) { + trace = subsetArrayAttrs(trace, matches); + } + // reach into the full trace object so we can properly reflect the + // selection attributes in every view + var d = this.gd._fullData[i]; + + /* + / Recursively inherit selection attributes from various sources, + / in order of preference: + / (1) official plotly.js selected attribute + / (2) highlight(selected = attrs_selected(...)) + */ + // TODO: it would be neat to have a dropdown to dynamically specify these! + $.extend(true, trace, this.highlight.selected); + + // if it is defined, override color with the "dynamic brush color"" + if (d.marker) { + trace.marker = trace.marker || {}; + trace.marker.color = selectionColour || trace.marker.color || d.marker.color; + } + if (d.line) { + trace.line = trace.line || {}; + trace.line.color = selectionColour || trace.line.color || d.line.color; + } + if (d.textfont) { + trace.textfont = trace.textfont || {}; + trace.textfont.color = selectionColour || trace.textfont.color || d.textfont.color; + } + if (d.fillcolor) { + // TODO: should selectionColour inherit alpha from the existing fillcolor? + trace.fillcolor = selectionColour || trace.fillcolor || d.fillcolor; + } + // attach a sensible name/legendgroup + trace.name = trace.name || keys.join("
"); + trace.legendgroup = trace.legendgroup || keys.join("
"); + + // keep track of mapping between this new trace and the trace it targets + // (necessary for updating frames to reflect the selection traces) + trace._originalIndex = i; + trace._newIndex = this.gd._fullData.length + traces.length; + trace._isCrosstalkTrace = true; + traces.push(trace); + } + } + + if (traces.length > 0) { + + Plotly.addTraces(this.gd, traces).then(function(gd) { + // incrementally add selection traces to frames + // (this is heavily inspired by Plotly.Plots.modifyFrames() + // in src/plots/plots.js) + var _hash = gd._transitionData._frameHash; + var _frames = gd._transitionData._frames || []; + + for (var i = 0; i < _frames.length; i++) { + + // add to _frames[i].traces *if* this frame references selected trace(s) + var newIndices = []; + for (var j = 0; j < traces.length; j++) { + var tr = traces[j]; + if (_frames[i].traces.indexOf(tr._originalIndex) > -1) { + newIndices.push(tr._newIndex); + _frames[i].traces.push(tr._newIndex); + } + } + + // nothing to do... + if (newIndices.length === 0) { + continue; + } + + var ctr = 0; + var nFrameTraces = _frames[i].data.length; + + for (var j = 0; j < nFrameTraces; j++) { + var frameTrace = _frames[i].data[j]; + if (!frameTrace.key || frameTrace.set !== group) { + continue; + } + + var matchFunc = getMatchFunc(frameTrace); + var matches = matchFunc(frameTrace.key, keys); + + if (matches.length > 0) { + if (!trace._isSimpleKey) { + frameTrace = subsetArrayAttrs(frameTrace, matches); + } + var d = gd._fullData[newIndices[ctr]]; + if (d.marker) { + frameTrace.marker = d.marker; + } + if (d.line) { + frameTrace.line = d.line; + } + if (d.textfont) { + frameTrace.textfont = d.textfont; + } + ctr = ctr + 1; + _frames[i].data.push(frameTrace); + } + } + + // update gd._transitionData._frameHash + _hash[_frames[i].name] = _frames[i]; + } + + }); + + // dim traces that have a set matching the set of selection sets + var tracesToDim = [], + opacities = [], + sets = Object.keys(this.groupSelections), + n = this.origData.length; + + for (var i = 0; i < n; i++) { + var opacity = this.origOpacity[i] || 1; + // have we already dimmed this trace? Or is this even worth doing? + if (opacity !== this.gd._fullData[i].opacity || this.highlight.opacityDim === 1) { + continue; + } + // is this set an element of the set of selection sets? + var matches = findMatches(sets, [this.gd.data[i].set]); + if (matches.length) { + tracesToDim.push(i); + opacities.push(opacity * this.highlight.opacityDim); + } + } + + if (tracesToDim.length > 0) { + Plotly.restyle(this.gd, {"opacity": opacities}, tracesToDim); + // turn off the selected/unselected API + Plotly.restyle(this.gd, {"selectedpoints": null}); + } + + } + + } +}; + +/* +Note: in all of these match functions, we assume needleSet (i.e. the selected keys) +is a 1D (or flat) array. The real difference is the meaning of haystack. +findMatches() does the usual thing you'd expect for +linked brushing on a scatterplot matrix. findSimpleMatches() returns a match iff +haystack is a subset of the needleSet. findNestedMatches() returns +*/ + +function getMatchFunc(trace) { + return (trace._isNestedKey) ? findNestedMatches : + (trace._isSimpleKey) ? findSimpleMatches : findMatches; +} + +// find matches for "flat" keys +function findMatches(haystack, needleSet) { + var matches = []; + haystack.forEach(function(obj, i) { + if (obj === null || needleSet.indexOf(obj) >= 0) { + matches.push(i); + } + }); + return matches; +} + +// find matches for "simple" keys +function findSimpleMatches(haystack, needleSet) { + var match = haystack.every(function(val) { + return val === null || needleSet.indexOf(val) >= 0; + }); + // yes, this doesn't make much sense other than conforming + // to the output type of the other match functions + return (match) ? [0] : [] +} + +// find matches for a "nested" haystack (2D arrays) +function findNestedMatches(haystack, needleSet) { + var matches = []; + for (var i = 0; i < haystack.length; i++) { + var hay = haystack[i]; + var match = hay.every(function(val) { + return val === null || needleSet.indexOf(val) >= 0; + }); + if (match) { + matches.push(i); + } + } + return matches; +} + +function isPlainObject(obj) { + return ( + Object.prototype.toString.call(obj) === '[object Object]' && + Object.getPrototypeOf(obj) === Object.prototype + ); +} + +function subsetArrayAttrs(obj, indices) { + var newObj = {}; + Object.keys(obj).forEach(function(k) { + var val = obj[k]; + + if (k.charAt(0) === "_") { + newObj[k] = val; + } else if (k === "transforms" && Array.isArray(val)) { + newObj[k] = val.map(function(transform) { + return subsetArrayAttrs(transform, indices); + }); + } else if (k === "colorscale" && Array.isArray(val)) { + newObj[k] = val; + } else if (isPlainObject(val)) { + newObj[k] = subsetArrayAttrs(val, indices); + } else if (Array.isArray(val)) { + newObj[k] = subsetArray(val, indices); + } else { + newObj[k] = val; + } + }); + return newObj; +} + +function subsetArray(arr, indices) { + var result = []; + for (var i = 0; i < indices.length; i++) { + result.push(arr[indices[i]]); + } + return result; +} + +// Convenience function for removing plotly's brush +function removeBrush(el) { + var outlines = el.querySelectorAll(".select-outline"); + for (var i = 0; i < outlines.length; i++) { + outlines[i].remove(); + } +} + + +// https://davidwalsh.name/javascript-debounce-function + +// Returns a function, that, as long as it continues to be invoked, will not +// be triggered. The function will be called after it stops being called for +// N milliseconds. If `immediate` is passed, trigger the function on the +// leading edge, instead of the trailing. +function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +}; diff --git a/docs/index.html b/docs/index.html index c68da1ce..edf873b5 100644 --- a/docs/index.html +++ b/docs/index.html @@ -196,7 +196,7 @@

Steve On Data

+
Categories
All (235)
abline (1)
apply (1)
arrow (1)
attributes (1)
augment (1)
autoarima (1)
automl (1)
batchfile (1)
benchmark (5)
bootstrap (4)
box (1)
brvm (1)
cci30 (1)
cms (1)
code (88)
correlation (1)
crypto (1)
cumulative (2)
data (2)
datatable (5)
datetime (4)
distribution (5)
distributions (1)
dplyr (3)
duckdb (1)
excel (6)
files (1)
ggplot2 (2)
healthcare (1)
healthyr (8)
healthyrai (18)
healthyrdata (2)
healthyrts (19)
healthyverse (1)
histograms (2)
kmeans (2)
knn (1)
lapply (5)
linear (1)
linearequations (1)
linkedin (1)
list (3)
lists (5)
mapping (2)
markets (1)
metadata (1)
mixturemodels (1)
modelr (1)
news (1)
openxlsx (2)
parsnip (1)
plotly (1)
plots (1)
preprocessor (1)
purrr (10)
randomwalk (2)
readr (1)
readxl (2)
recipes (3)
regex (1)
regression (1)
rtip (223)
rvest (1)
sample (1)
sapply (1)
shell (1)
shiny (16)
simulation (1)
skew (1)
sql (1)
subset (1)
thanks (1)
tidyaml (11)
tidydensity (22)
tidymodels (9)
tidyquant (1)
tidyr (1)
timeseries (26)
transforms (1)
vba (2)
viz (47)
weeklytip (13)
which (1)
workflowsets (1)
writexl (2)
xgboost (2)
xlsx (2)
@@ -209,7 +209,48 @@
Categories
-
+
+
+

+
+ + +
+
@@ -244,7 +285,7 @@

-
+
@@ -279,7 +320,7 @@

-
+
@@ -314,7 +355,7 @@

-
+
@@ -352,7 +393,7 @@

-
+
@@ -387,7 +428,7 @@

-
+
@@ -428,7 +469,7 @@

-
+
@@ -466,7 +507,7 @@

-
+
@@ -504,7 +545,7 @@

-
+
@@ -542,7 +583,7 @@

-
+
@@ -580,7 +621,7 @@

-
+
@@ -618,7 +659,7 @@

-
+
@@ -656,7 +697,7 @@

-
+
@@ -694,7 +735,7 @@

-
+
@@ -732,7 +773,7 @@

-
+
@@ -773,7 +814,7 @@

-
+
@@ -811,7 +852,7 @@

-
+
@@ -849,7 +890,7 @@

-
+
@@ -890,7 +931,7 @@

-
+
@@ -931,7 +972,7 @@

-
+
@@ -972,7 +1013,7 @@

-
+
@@ -1010,7 +1051,7 @@

-
+
@@ -1048,7 +1089,7 @@

-
+
@@ -1086,7 +1127,7 @@

-
+
@@ -1124,9 +1165,9 @@

-
+
-
+
@@ -1200,7 +1241,7 @@

-
+
@@ -1238,7 +1279,7 @@

-
+
@@ -1276,7 +1317,7 @@

-
+
@@ -1314,7 +1355,7 @@

-
+
@@ -1352,7 +1393,7 @@

-
+
@@ -1390,7 +1431,7 @@

-
+
@@ -1428,7 +1469,7 @@

-
+
@@ -1469,7 +1510,7 @@

-
+
@@ -1507,7 +1548,7 @@

-
+
@@ -1545,7 +1586,7 @@

-
+
@@ -1583,7 +1624,7 @@

-
+
@@ -1621,7 +1662,7 @@

-
+
@@ -1659,7 +1700,7 @@

-
+
@@ -1697,7 +1738,7 @@

-
+
@@ -1735,7 +1776,7 @@

-
+
@@ -1773,7 +1814,7 @@

-
+
@@ -1811,7 +1852,7 @@

-
+
@@ -1849,7 +1890,7 @@

-
+
@@ -1887,7 +1928,7 @@

-
+
@@ -1925,7 +1966,7 @@

-
+
@@ -1963,7 +2004,7 @@

-
+
@@ -2001,7 +2042,7 @@

-
+
@@ -2039,7 +2080,7 @@

-
+
@@ -2077,7 +2118,7 @@

-
+
@@ -2118,7 +2159,7 @@

-
+
@@ -2156,7 +2197,7 @@

-
+
@@ -2194,7 +2235,7 @@

-
+
@@ -2232,7 +2273,7 @@

-
+
@@ -2270,7 +2311,7 @@

-
+
@@ -2308,7 +2349,7 @@

-
+
@@ -2346,7 +2387,7 @@

-
+
@@ -2384,7 +2425,7 @@

-
+
@@ -2422,7 +2463,7 @@

-
+
@@ -2457,7 +2498,7 @@

-
+
@@ -2495,7 +2536,7 @@

-
+
@@ -2533,7 +2574,7 @@

-
+
@@ -2568,7 +2609,7 @@

-
+
@@ -2603,7 +2644,7 @@

-
+
@@ -2638,7 +2679,7 @@

-
+
@@ -2673,7 +2714,7 @@

-
+
@@ -2708,7 +2749,7 @@

-
+
@@ -2743,7 +2784,7 @@

-
+
@@ -2778,7 +2819,7 @@

-
+
@@ -2813,7 +2854,7 @@

-
+
@@ -2848,7 +2889,7 @@

-
+
@@ -2883,7 +2924,7 @@

-
+
@@ -2918,7 +2959,7 @@

-
+
@@ -2953,7 +2994,7 @@

-
+
@@ -2991,7 +3032,7 @@

-
+
@@ -3026,7 +3067,7 @@

-
+
@@ -3061,7 +3102,7 @@

-
+
@@ -3096,7 +3137,7 @@

-
+
@@ -3131,7 +3172,7 @@

-
+
@@ -3166,7 +3207,7 @@

-
+
@@ -3207,7 +3248,7 @@

-
+
@@ -3251,7 +3292,7 @@

-
+
@@ -3289,7 +3330,7 @@

-
+
@@ -3324,7 +3365,7 @@

-
+
@@ -3359,7 +3400,7 @@

-
+
@@ -3394,7 +3435,7 @@

-
+
@@ -3429,7 +3470,7 @@

-
+
@@ -3464,7 +3505,7 @@

-
+
@@ -3502,7 +3543,7 @@

-
+
@@ -3540,7 +3581,7 @@

-
+
@@ -3575,7 +3616,7 @@

-
+
@@ -3616,7 +3657,7 @@

-
+
@@ -3660,7 +3701,7 @@

-
+
@@ -3695,7 +3736,7 @@

-
+
@@ -3730,7 +3771,7 @@

-
+
@@ -3765,7 +3806,7 @@

-
+
@@ -3800,7 +3841,7 @@

-
+
@@ -3841,7 +3882,7 @@

-
+
@@ -3882,7 +3923,7 @@

-
+
@@ -3917,7 +3958,7 @@

-
+
@@ -3952,7 +3993,7 @@

-
+
@@ -3990,7 +4031,7 @@

-
+
@@ -4025,7 +4066,7 @@

-
+
@@ -4060,7 +4101,7 @@

-
+
@@ -4098,7 +4139,7 @@

-
+
@@ -4133,7 +4174,7 @@

-
+
@@ -4180,7 +4221,7 @@

-
+
@@ -4227,7 +4268,7 @@

-
+
@@ -4271,7 +4312,7 @@

-
+
@@ -4306,7 +4347,7 @@

-
+
@@ -4344,7 +4385,7 @@

-
+
@@ -4382,7 +4423,7 @@

-
+
@@ -4420,7 +4461,7 @@

-
+
@@ -4458,7 +4499,7 @@

-
+
@@ -4496,7 +4537,7 @@

-
+
@@ -4534,7 +4575,7 @@

-
+
@@ -4572,7 +4613,7 @@

-
+
@@ -4613,7 +4654,7 @@

-
+
@@ -4654,7 +4695,7 @@

-
+
@@ -4692,7 +4733,7 @@

-
+
@@ -4733,7 +4774,7 @@

-
+
@@ -4774,7 +4815,7 @@

-
+
@@ -4818,7 +4859,7 @@

-
+
@@ -4859,7 +4900,7 @@

-
+
@@ -4903,7 +4944,7 @@

-
+
@@ -4947,7 +4988,7 @@

-
+
@@ -4991,7 +5032,7 @@

-
+
@@ -5035,7 +5076,7 @@

-
+
@@ -5079,7 +5120,7 @@

-
+
@@ -5120,7 +5161,7 @@

-
+
@@ -5161,7 +5202,7 @@

-
+
@@ -5202,7 +5243,7 @@

-
+
@@ -5243,7 +5284,7 @@

-
+
@@ -5281,7 +5322,7 @@

-
+
@@ -5325,7 +5366,7 @@

-
+
@@ -5366,7 +5407,7 @@

-
+
@@ -5413,7 +5454,7 @@

-
+
@@ -5457,7 +5498,7 @@

-
+
@@ -5504,7 +5545,7 @@

-
+
@@ -5545,7 +5586,7 @@

-
+
@@ -5595,7 +5636,7 @@

-
+
@@ -5633,7 +5674,7 @@

-
+
@@ -5671,7 +5712,7 @@

-
+
@@ -5709,7 +5750,7 @@

-
+
@@ -5747,7 +5788,7 @@

-
+
@@ -5782,7 +5823,7 @@

-
+
@@ -5823,7 +5864,7 @@

-
+
@@ -5867,7 +5908,7 @@

-
+
@@ -5908,7 +5949,7 @@

-
+
@@ -5949,7 +5990,7 @@

-
+
@@ -5990,7 +6031,7 @@

-
+
@@ -6031,7 +6072,7 @@

-
+
@@ -6075,7 +6116,7 @@

-
+
@@ -6119,7 +6160,7 @@

-
+
@@ -6160,7 +6201,7 @@

-
+
@@ -6204,7 +6245,7 @@

-
+
@@ -6248,7 +6289,7 @@

-
+
@@ -6292,7 +6333,7 @@

-
+
@@ -6339,7 +6380,7 @@

-
+
@@ -6380,7 +6421,7 @@

-
+
@@ -6424,7 +6465,7 @@

-
+
@@ -6465,7 +6506,7 @@

-
+
@@ -6509,7 +6550,7 @@

-
+
@@ -6550,7 +6591,7 @@

-
+
@@ -6591,7 +6632,7 @@

-
+
@@ -6629,7 +6670,7 @@

-
+
@@ -6670,7 +6711,7 @@

-
+
@@ -6711,7 +6752,7 @@

-
+
@@ -6758,7 +6799,7 @@

-
+
@@ -6802,7 +6843,7 @@

-
+
@@ -6846,7 +6887,7 @@

-
+
@@ -6890,7 +6931,7 @@

-
+
@@ -6934,7 +6975,7 @@

-
+
@@ -6978,7 +7019,7 @@

-
+
@@ -7022,7 +7063,7 @@

-
+
@@ -7066,7 +7107,7 @@

-
+
@@ -7110,7 +7151,7 @@

-
+
@@ -7157,7 +7198,7 @@

-
+
@@ -7198,7 +7239,7 @@

-
+
@@ -7242,7 +7283,7 @@

-
+
@@ -7286,7 +7327,7 @@

-
+
@@ -7330,7 +7371,7 @@

-
+
@@ -7374,7 +7415,7 @@

-
+
@@ -7418,7 +7459,7 @@

-
+
@@ -7462,7 +7503,7 @@

-
+
@@ -7503,7 +7544,7 @@

-
+
@@ -7547,7 +7588,7 @@

-
+
@@ -7588,7 +7629,7 @@

-
+
@@ -7629,7 +7670,7 @@

-
+
@@ -7673,7 +7714,7 @@

-
+
@@ -7717,7 +7758,7 @@

-
+
@@ -7761,7 +7802,7 @@

-
+
@@ -7802,7 +7843,7 @@

-
+
@@ -7849,7 +7890,7 @@

-
+
@@ -7890,7 +7931,7 @@

-
+
@@ -7931,7 +7972,7 @@

-
+
@@ -7975,7 +8016,7 @@

-
+
@@ -8019,7 +8060,7 @@

-
+
@@ -8063,7 +8104,7 @@

-
+
@@ -8107,7 +8148,7 @@

-
+
@@ -8148,7 +8189,7 @@

-
+
@@ -8192,7 +8233,7 @@

-
+
@@ -8236,7 +8277,7 @@

-
+
@@ -8280,7 +8321,7 @@

-
+
@@ -8324,7 +8365,7 @@

-
+
@@ -8368,7 +8409,7 @@

-
+
@@ -8412,7 +8453,7 @@

-
+
@@ -8456,7 +8497,7 @@

-
+
@@ -8497,7 +8538,7 @@

-
+
@@ -8541,7 +8582,7 @@

-
+
@@ -8585,7 +8626,7 @@

-
+
@@ -8626,7 +8667,7 @@

-
+
@@ -8670,7 +8711,7 @@

-
+
@@ -8714,7 +8755,7 @@

-
+
@@ -8755,7 +8796,7 @@

-
+
@@ -8796,7 +8837,7 @@

-
+
@@ -8840,7 +8881,7 @@

-
+
@@ -8881,7 +8922,7 @@

-
+
@@ -8925,7 +8966,7 @@

-
+
@@ -8969,7 +9010,7 @@

-
+
@@ -9013,7 +9054,7 @@

-
+
@@ -9054,7 +9095,7 @@

-
+
@@ -9098,7 +9139,7 @@

-
+
@@ -9139,7 +9180,7 @@

-
+
@@ -9180,7 +9221,7 @@

-
+
@@ -9224,7 +9265,7 @@

-
+
@@ -9262,7 +9303,7 @@

-
+
@@ -9306,7 +9347,7 @@

-
+
@@ -9347,7 +9388,7 @@

-
+
@@ -9394,7 +9435,7 @@

-
+
@@ -9435,7 +9476,7 @@

-
+
@@ -9476,7 +9517,7 @@

-
+
@@ -9517,7 +9558,7 @@

-
+
@@ -9558,7 +9599,7 @@

-
+
diff --git a/docs/index.xml b/docs/index.xml index 49273afe..9d92a237 100644 --- a/docs/index.xml +++ b/docs/index.xml @@ -10,7 +10,443 @@ quarto-1.3.433 -Wed, 15 Nov 2023 05:00:00 GMT +Thu, 16 Nov 2023 05:00:00 GMT + + {healthyR.ts} New Features: Unlocking More Power + Steven P. Sanderson II, MPH + https://www.spsanderson.com/steveondata/posts/2011-11-16/index.html + +

New Features: Unlocking More Power

+

My R package {healthyR.ts} has been updated to version 0.3.0; you can install it from either CRAN, r-universe or GitHub. Let’s go over some of the changes and improvements.

+ +
+

News

+
+

1. util_log_ts() - Logging Time Series Data

+

One of the standout additions is the introduction of util_log_ts(). This function seems like a game-changer, providing a streamlined way to log time series data. This is incredibly useful, especially when dealing with extensive datasets, making the whole process more efficient and user-friendly. This is a helper function for auto_stationarize().

+
+
+

2. util_singlediff_ts() - Single Differences for Time Series

+

The addition of util_singlediff_ts() expands the toolkit, offering a function dedicated to handling single differences in time series data. This is valuable for various applications, such as identifying trends or preparing data for further analysis. This is a helper function for auto_stationarize().

+
+
+

3. util_doublediff_ts() - Double Differences for Time Series

+

Building on the concept of differencing, util_doublediff_ts() seems to provide a higher level of sophistication, allowing users to perform double differences on time series data. This could be pivotal in cases where a more refined analysis is required. This is a helper function for auto_stationarize().

+
+
+

4. util_difflog_ts() - Combining Differences and Log Transformation

+

The fusion of differencing and log transformation in util_difflog_ts() is a remarkable addition. This could be particularly beneficial in scenarios where both operations are needed to unlock deeper insights from the time series data. This is a helper function for auto_stationarize().

+
+
+

5. util_doubledifflog_ts() - Double Differences with Log Transformation

+

The introduction of util_doubledifflog_ts() appears to take things a step further by combining double differences and log transformation. This function seems poised to provide a comprehensive solution for users dealing with complex time series data. This is a helper function for auto_stationarize().

+
+
+
+

Minor Fixes and Improvements: Polishing the Experience

+
+

1. Attributes Enhancement in ts_growth_rate_vec()

+

The attention to detail is evident with the addition of attributes to the output of ts_growth_rate_vec(). This enhancement not only improves the clarity of results but also contributes to a more informative and user-friendly experience.

+
+
+

2. Refinement of auto_stationarize() in Response to User Feedback

+

Updates to auto_stationarize() based on user feedback (Fix #481 #483) demonstrate a commitment to refining existing features. This responsiveness to the community’s needs is commendable and ensures that the package evolves in sync with user expectations. It has taken all of the util_ transforms mentioned above in order to improve it’s functionality.

+
+
+

3. Integration with auto_arima Engine in ts_auto_arima()

+

The integration of ts_auto_arima() with the parsnip engine of auto_arima is a notable improvement. This update, triggered when .tune is set to FALSE, aligns the package with cutting-edge tools, potentially enhancing the efficiency and accuracy of time series modeling.

+

In conclusion, the release of healthyR.ts version 0.3.0 is an exciting leap forward. The new features introduce powerful capabilities, while the minor fixes and improvements showcase a commitment to providing a robust and user-friendly package. Users can look forward to a more versatile and refined experience in time series analysis. Great job on this release, and I’m sure the community is eager to explore these enhancements!

+
+
+
+

Examples

+

Let’s see how the main functions now behave.

+
+

auto_stationarize()

+
+
library(healthyR.ts)
+
+auto_stationarize(AirPassengers)
+
+
The time series is already stationary via ts_adf_test().
+
+
+
     Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
+1949 112 118 132 129 121 135 148 148 136 119 104 118
+1950 115 126 141 135 125 149 170 170 158 133 114 140
+1951 145 150 178 163 172 178 199 199 184 162 146 166
+1952 171 180 193 181 183 218 230 242 209 191 172 194
+1953 196 196 236 235 229 243 264 272 237 211 180 201
+1954 204 188 235 227 234 264 302 293 259 229 203 229
+1955 242 233 267 269 270 315 364 347 312 274 237 278
+1956 284 277 317 313 318 374 413 405 355 306 271 306
+1957 315 301 356 348 355 422 465 467 404 347 305 336
+1958 340 318 362 348 363 435 491 505 404 359 310 337
+1959 360 342 406 396 420 472 548 559 463 407 362 405
+1960 417 391 419 461 472 535 622 606 508 461 390 432
+
+
auto_stationarize(BJsales)
+
+
The time series is not stationary. Attempting to make it stationary...
+
+
+
$stationary_ts
+Time Series:
+Start = 3 
+End = 150 
+Frequency = 1 
+  [1]  0.5 -0.4  0.6  1.1 -2.8  3.0 -1.1  0.6 -0.5 -0.5  0.1  2.0 -0.6  0.8  1.2
+ [16] -3.4 -0.7 -0.3  1.7  3.0 -3.2  0.9  2.2 -2.5 -0.4  2.6 -4.3  2.0 -3.1  2.7
+ [31] -2.1  0.1  2.1 -0.2 -2.2  0.6  1.0 -2.6  3.0  0.3  0.2 -0.8  1.0  0.0  3.2
+ [46] -2.2 -4.7  1.2  0.8 -0.6 -0.4  0.6  1.0 -1.6 -0.1  3.4 -0.9 -1.7 -0.5  0.8
+ [61]  2.4 -1.9  0.6 -2.2  2.6 -0.1 -2.7  1.7 -0.3  1.9 -2.7  1.1 -0.6  0.9  0.0
+ [76]  1.8 -0.5 -0.4 -1.2  2.6 -1.8  1.7 -0.9  0.6 -0.4  3.0 -2.8  3.1 -2.3 -1.1
+ [91]  2.1 -0.3 -1.7 -0.8 -0.4  1.1 -1.5  0.3  1.4 -2.0  1.3 -0.3  0.4 -3.5  1.1
+[106]  2.6  0.4 -1.3  2.0 -1.6  0.6 -0.1 -1.4  1.6  1.6 -3.4  1.7 -2.2  2.1 -2.0
+[121] -0.2  0.2  0.7 -1.4  1.8 -0.1 -0.7  0.4  0.4  1.0 -2.4  1.0 -0.4  0.8 -1.0
+[136]  1.4 -1.2  1.1 -0.9  0.5  1.9 -0.6  0.3 -1.4 -0.9 -0.5  1.4  0.1
+
+$ndiffs
+[1] 1
+
+$adf_stats
+$adf_stats$test_stat
+[1] -6.562008
+
+$adf_stats$p_value
+[1] 0.01
+
+
+$trans_type
+[1] "double_diff"
+
+$ret
+[1] TRUE
+
+
plot.ts(auto_stationarize(BJsales)$stationary_ts)
+
+
The time series is not stationary. Attempting to make it stationary...
+
+
+

+
+
auto_stationarize(BJsales.lead)
+
+
The time series is not stationary. Attempting to make it stationary...
+
+
+
$stationary_ts
+Time Series:
+Start = 2 
+End = 150 
+Frequency = 1 
+  [1]  0.06  0.25 -0.57  0.58 -0.20  0.23 -0.04 -0.19  0.03  0.42  0.04  0.24
+ [13]  0.34 -0.46 -0.18 -0.08  0.29  0.56 -0.37  0.20  0.54 -0.31  0.03  0.52
+ [25] -0.70  0.35 -0.63  0.44 -0.38 -0.01  0.22  0.10 -0.50  0.01  0.30 -0.76
+ [37]  0.52  0.15  0.06 -0.10  0.21 -0.01  0.70 -0.22 -0.76  0.06  0.02 -0.17
+ [49] -0.08  0.01  0.11 -0.39  0.01  0.50 -0.02 -0.37 -0.13  0.05  0.54 -0.46
+ [61]  0.25 -0.52  0.44  0.02 -0.47  0.11  0.06  0.25 -0.35  0.00 -0.06  0.21
+ [73] -0.09  0.36  0.09 -0.04 -0.20  0.44 -0.23  0.40 -0.01  0.17  0.08  0.58
+ [85] -0.27  0.79 -0.21  0.02  0.30  0.28 -0.27 -0.01  0.03  0.16 -0.28  0.15
+ [97]  0.26 -0.36  0.32 -0.11  0.22 -0.65  0.00  0.47  0.16 -0.19  0.48 -0.26
+[109]  0.21  0.00 -0.20  0.35  0.38 -0.48  0.20 -0.32  0.43 -0.50  0.12 -0.17
+[121]  0.15 -0.36  0.35 -0.03 -0.18  0.16  0.07  0.21 -0.50  0.23 -0.13  0.14
+[133] -0.15  0.19 -0.24  0.26 -0.22  0.17  0.37 -0.06  0.29 -0.34 -0.12 -0.16
+[145]  0.25  0.08 -0.07  0.26 -0.37
+
+$ndiffs
+[1] 1
+
+$adf_stats
+$adf_stats$test_stat
+[1] -4.838625
+
+$adf_stats$p_value
+[1] 0.01
+
+
+$trans_type
+[1] "diff"
+
+$ret
+[1] TRUE
+
+
plot.ts(auto_stationarize(BJsales.lead)$stationary_ts)
+
+
The time series is not stationary. Attempting to make it stationary...
+
+
+

+
+
+
+
+

ts_auto_arima()

+

This use to only use the Arima engine if the .tune parameter was set to FALSE, thus it would many times give a simple straight line forecast. This was changed to make the engine auto_arima if .tune is set to FALSE.

+
+
library(timetk)
+library(dplyr)
+library(modeltime)
+
+data <- AirPassengers |>
+  ts_to_tbl() |>
+  select(-index)
+
+splits <- time_series_split(
+  data
+  , date_col
+  , assess = 12
+  , skip = 3
+  , cumulative = TRUE
+)
+
+ts_aa <- ts_auto_arima(
+  .data = data,
+  .num_cores = 2,
+  .date_col = date_col,
+  .value_col = value,
+  .rsamp_obj = splits,
+  .formula = value ~ .,
+  .grid_size = 5,
+  .cv_slice_limit = 2,
+  .tune = FALSE
+)
+
+ts_aa$recipe_info
+
+
$recipe_call
+recipe(.data = data, .date_col = date_col, .value_col = value, 
+    .formula = value ~ ., .rsamp_obj = splits, .tune = FALSE, 
+    .grid_size = 5, .num_cores = 2, .cv_slice_limit = 2)
+
+$recipe_syntax
+[1] "ts_arima_recipe <-"                                                                                                                                                                           
+[2] "\n  recipe(.data = data, .date_col = date_col, .value_col = value, .formula = value ~ \n    ., .rsamp_obj = splits, .tune = FALSE, .grid_size = 5, .num_cores = 2, \n    .cv_slice_limit = 2)"
+
+$rec_obj
+
+
ts_aa$model_info
+
+
$model_spec
+ARIMA Regression Model Specification (regression)
+
+Computational engine: auto_arima 
+
+
+$wflw
+══ Workflow ════════════════════════════════════════════════════════════════════
+Preprocessor: Recipe
+Model: arima_reg()
+
+── Preprocessor ────────────────────────────────────────────────────────────────
+0 Recipe Steps
+
+── Model ───────────────────────────────────────────────────────────────────────
+ARIMA Regression Model Specification (regression)
+
+Computational engine: auto_arima 
+
+
+$fitted_wflw
+══ Workflow [trained] ══════════════════════════════════════════════════════════
+Preprocessor: Recipe
+Model: arima_reg()
+
+── Preprocessor ────────────────────────────────────────────────────────────────
+0 Recipe Steps
+
+── Model ───────────────────────────────────────────────────────────────────────
+Series: outcome 
+ARIMA(1,1,0)(0,1,0)[12] 
+
+Coefficients:
+          ar1
+      -0.2431
+s.e.   0.0894
+
+sigma^2 = 109.8:  log likelihood = -447.95
+AIC=899.9   AICc=900.01   BIC=905.46
+
+$was_tuned
+[1] "not_tuned"
+
+
ts_aa$model_calibration
+
+
$plot
+
+$calibration_tbl
+# Modeltime Table
+# A tibble: 1 × 5
+  .model_id .model     .model_desc             .type .calibration_data
+      <int> <list>     <chr>                   <chr> <list>           
+1         1 <workflow> ARIMA(1,1,0)(0,1,0)[12] Test  <tibble [12 × 4]>
+
+$model_accuracy
+# A tibble: 1 × 9
+  .model_id .model_desc             .type   mae  mape  mase smape  rmse   rsq
+      <int> <chr>                   <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
+1         1 ARIMA(1,1,0)(0,1,0)[12] Test   18.5  4.18 0.384  4.03  23.9 0.955
+
+
ts_aa$model_calibration$plot
+
+
+ +
+
+

Finally enhancement to add attributes to ts_growth_rate_vec()

+
+
ts_growth_rate_vec(AirPassengers)
+
+
  [1]          NA   5.3571429  11.8644068  -2.2727273  -6.2015504  11.5702479
+  [7]   9.6296296   0.0000000  -8.1081081 -12.5000000 -12.6050420  13.4615385
+ [13]  -2.5423729   9.5652174  11.9047619  -4.2553191  -7.4074074  19.2000000
+ [19]  14.0939597   0.0000000  -7.0588235 -15.8227848 -14.2857143  22.8070175
+ [25]   3.5714286   3.4482759  18.6666667  -8.4269663   5.5214724   3.4883721
+ [31]  11.7977528   0.0000000  -7.5376884 -11.9565217  -9.8765432  13.6986301
+ [37]   3.0120482   5.2631579   7.2222222  -6.2176166   1.1049724  19.1256831
+ [43]   5.5045872   5.2173913 -13.6363636  -8.6124402  -9.9476440  12.7906977
+ [49]   1.0309278   0.0000000  20.4081633  -0.4237288  -2.5531915   6.1135371
+ [55]   8.6419753   3.0303030 -12.8676471 -10.9704641 -14.6919431  11.6666667
+ [61]   1.4925373  -7.8431373  25.0000000  -3.4042553   3.0837004  12.8205128
+ [67]  14.3939394  -2.9801325 -11.6040956 -11.5830116 -11.3537118  12.8078818
+ [73]   5.6768559  -3.7190083  14.5922747   0.7490637   0.3717472  16.6666667
+ [79]  15.5555556  -4.6703297 -10.0864553 -12.1794872 -13.5036496  17.2995781
+ [85]   2.1582734  -2.4647887  14.4404332  -1.2618297   1.5974441  17.6100629
+ [91]  10.4278075  -1.9370460 -12.3456790 -13.8028169 -11.4379085  12.9151292
+ [97]   2.9411765  -4.4444444  18.2724252  -2.2471910   2.0114943  18.8732394
+[103]  10.1895735   0.4301075 -13.4903640 -14.1089109 -12.1037464  10.1639344
+[109]   1.1904762  -6.4705882  13.8364780  -3.8674033   4.3103448  19.8347107
+[115]  12.8735632   2.8513238 -20.0000000 -11.1386139 -13.6490251   8.7096774
+[121]   6.8249258  -5.0000000  18.7134503  -2.4630542   6.0606061  12.3809524
+[127]  16.1016949   2.0072993 -17.1735242 -12.0950324 -11.0565111  11.8784530
+[133]   2.9629630  -6.2350120   7.1611253  10.0238663   2.3861171  13.3474576
+[139]  16.2616822  -2.5723473 -16.1716172  -9.2519685 -15.4013015  10.7692308
+attr(,"vector_attributes")
+attr(,"vector_attributes")$tsp
+[1] 1949.000 1960.917   12.000
+
+attr(,"vector_attributes")$class
+[1] "ts"
+
+attr(,"name")
+[1] "AirPassengers"
+
+
+ + +
+
+ + ]]>
+ rtip + healthyrts + timeseries + https://www.spsanderson.com/steveondata/posts/2011-11-16/index.html + Thu, 16 Nov 2023 05:00:00 GMT +
How to Perform Multiple Linear Regression in R Steven P. Sanderson II, MPH @@ -3880,260 +4316,5 @@ $p_value https://www.spsanderson.com/steveondata/posts/2023-10-17/index.html Tue, 17 Oct 2023 04:00:00 GMT - - Analyzing Time Series Growth with ts_growth_rate_vec() in healthyR.ts - Steven P. Sanderson II, MPH - https://www.spsanderson.com/steveondata/posts/2023-10-16/index.html - -

Introduction

-

Time series data is essential for understanding trends and making forecasts in various fields, from finance to healthcare. Analyzing the growth rate of time series data is a crucial step in uncovering valuable insights. In the world of R programming, the healthyR.ts library introduces a powerful tool to calculate growth rates and log-differenced growth rates with the ts_growth_rate_vec() function. In this blog post, we’ll explore how this function works and how it can be used for effective time series analysis.

- -
-

Understanding ts_growth_rate_vec():

-

The ts_growth_rate_vec() function is part of the healthyR.ts library, designed to work with numeric vectors or time series data. It calculates the growth rate or log-differenced growth rate of the provided data, offering valuable insights into the underlying trends and patterns.

-
-
-

Syntax

-

Here is the function syntax:

-
ts_growth_rate_vec(
-  .x, 
-  .scale = 100, 
-  .power = 1, 
-  .log_diff = FALSE, 
-  .lags = 1
-)
-
    -
  • .x - A numeric vector
  • -
  • .scale - A numeric value that is used to scale the output
  • -
  • .power - A numeric value that is used to raise the output to a power
  • -
  • .log_diff - A logical value that determines whether the output is a log difference
  • -
  • .lags - An integer that determines the number of lags to use
  • -
-

You can find the documentation here

-
-
-

Examples

-

Let’s first take a look at the data we are going to be working with in this post, AirPassengers.

-
-
AirPassengers
-
-
     Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
-1949 112 118 132 129 121 135 148 148 136 119 104 118
-1950 115 126 141 135 125 149 170 170 158 133 114 140
-1951 145 150 178 163 172 178 199 199 184 162 146 166
-1952 171 180 193 181 183 218 230 242 209 191 172 194
-1953 196 196 236 235 229 243 264 272 237 211 180 201
-1954 204 188 235 227 234 264 302 293 259 229 203 229
-1955 242 233 267 269 270 315 364 347 312 274 237 278
-1956 284 277 317 313 318 374 413 405 355 306 271 306
-1957 315 301 356 348 355 422 465 467 404 347 305 336
-1958 340 318 362 348 363 435 491 505 404 359 310 337
-1959 360 342 406 396 420 472 548 559 463 407 362 405
-1960 417 391 419 461 472 535 622 606 508 461 390 432
-
-
plot(AirPassengers)
-
-

-
-
-

Let’s load in the {healthyR.ts} library and see some examples to illustrate its functionality:

-
-
library(healthyR.ts)
-
-
    -
  1. Calculating Basic Growth Rate:
  2. -
-
-
ts_growth_rate_vec(AirPassengers) |> head(12)
-
-
 [1]         NA   5.357143  11.864407  -2.272727  -6.201550  11.570248
- [7]   9.629630   0.000000  -8.108108 -12.500000 -12.605042  13.461538
-
-
plot(ts(ts_growth_rate_vec(AirPassengers)))
-
-

-
-
-

The output provides growth rates for the AirPassengers dataset. This basic calculation can help you understand how the data is evolving over time. The growth rates are calculated from one point to the next, giving you an idea of the speed at which the values are changing.

-
    -
  1. Applying Scaling and Power Transformation:
  2. -
-
-
ts_growth_rate_vec(AirPassengers, .log_diff = TRUE) |> head(12)
-
-
 [1]         NA   5.218575  11.211730  -2.298952  -6.402186  10.948423
- [7]   9.193750   0.000000  -8.455739 -13.353139 -13.473259  12.629373
-
-
plot(ts(ts_growth_rate_vec(AirPassengers, .log_diff = TRUE)))
-
-

-
-
-

This example introduces the option to apply scaling and a power transformation. The resulting growth rates can help uncover trends that might not be apparent in the original data. Using a log-differenced growth rate is particularly useful for capturing the percentage change, making it easier to interpret the data.

-
    -
  1. Handling Lagged Data:
  2. -
-
-
ts_growth_rate_vec(AirPassengers, .lags = -1) |> head(12)
-
-
 [1]  -5.084746 -10.606061   2.325581   6.611570 -10.370370  -8.783784
- [7]   0.000000   8.823529  14.285714  14.423077 -11.864407   2.608696
-
-
plot.ts(ts_growth_rate_vec(AirPassengers, .lags = -1))
-
-

-
-
-

In this case, the function calculates the log differences of the time series with lags. This is helpful when you want to observe the changes between data points at different time intervals. It can reveal patterns that might not be apparent in the basic growth rate calculation.

-
    -
  1. Combining Scaling, Transformation, and Lags:
  2. -
-
-
ts_growth_rate_vec(AirPassengers, .log_diff = TRUE, .lags = -1) |> head(12)
-
-
 [1]  -5.218575 -11.211730   2.298952   6.402186 -10.948423  -9.193750
- [7]   0.000000   8.455739  13.353139  13.473259 -12.629373   2.575250
-
-
plot.ts(ts_growth_rate_vec(AirPassengers, .log_diff = TRUE, .lags = -1))
-
-

-
-
-

This example combines all the mentioned features to provide a comprehensive analysis of the data. It’s a powerful way to understand how the growth rate is affected by various factors, such as scaling and time lags.

-
-
-

Conclusion:

-

The ts_growth_rate_vec() function in the healthyR.ts library is a versatile tool for time series analysis. Whether you need a basic growth rate, want to apply scaling and transformation, or work with lagged data, this function has you covered. It’s a valuable asset for R programmers, helping them uncover hidden insights within time series data.

-

Incorporating this function into your data analysis workflow can provide you with a deeper understanding of how values change over time. Whether you’re working with financial data, healthcare data, or any other time series dataset, ts_growth_rate_vec() is a powerful addition to your R programming toolkit. Start exploring your time series data today and discover the trends and patterns that lie within.

- - -
- - ]]>
- rtip - healthyrts - timeseries - https://www.spsanderson.com/steveondata/posts/2023-10-16/index.html - Mon, 16 Oct 2023 04:00:00 GMT -
diff --git a/docs/listings.json b/docs/listings.json index de3ef55f..62295dc7 100644 --- a/docs/listings.json +++ b/docs/listings.json @@ -2,6 +2,7 @@ { "listing": "/index.html", "items": [ + "/posts/2011-11-16/index.html", "/posts/2023-11-15/index.html", "/posts/2023-11-14/index.html", "/posts/2023-11-13/index.html", diff --git a/docs/posts/2011-11-16/index.html b/docs/posts/2011-11-16/index.html new file mode 100644 index 00000000..fcdb281f --- /dev/null +++ b/docs/posts/2011-11-16/index.html @@ -0,0 +1,914 @@ + + + + + + + + + + + +Steve’s Data Tips and Tricks - {healthyR.ts} New Features: Unlocking More Power + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+ +
+
+
+

{healthyR.ts} New Features: Unlocking More Power

+
+
rtip
+
healthyrts
+
timeseries
+
+
+
+ + +
+ +
+
Author
+
+

Steven P. Sanderson II, MPH

+
+
+ +
+
Published
+
+

November 16, 2023

+
+
+ + +
+ + +
+ + + + +
+ + + + +
+

New Features: Unlocking More Power

+

My R package {healthyR.ts} has been updated to version 0.3.0; you can install it from either CRAN, r-universe or GitHub. Let’s go over some of the changes and improvements.

+
+
+

News

+
+

1. util_log_ts() - Logging Time Series Data

+

One of the standout additions is the introduction of util_log_ts(). This function seems like a game-changer, providing a streamlined way to log time series data. This is incredibly useful, especially when dealing with extensive datasets, making the whole process more efficient and user-friendly. This is a helper function for auto_stationarize().

+
+
+

2. util_singlediff_ts() - Single Differences for Time Series

+

The addition of util_singlediff_ts() expands the toolkit, offering a function dedicated to handling single differences in time series data. This is valuable for various applications, such as identifying trends or preparing data for further analysis. This is a helper function for auto_stationarize().

+
+
+

3. util_doublediff_ts() - Double Differences for Time Series

+

Building on the concept of differencing, util_doublediff_ts() seems to provide a higher level of sophistication, allowing users to perform double differences on time series data. This could be pivotal in cases where a more refined analysis is required. This is a helper function for auto_stationarize().

+
+
+

4. util_difflog_ts() - Combining Differences and Log Transformation

+

The fusion of differencing and log transformation in util_difflog_ts() is a remarkable addition. This could be particularly beneficial in scenarios where both operations are needed to unlock deeper insights from the time series data. This is a helper function for auto_stationarize().

+
+
+

5. util_doubledifflog_ts() - Double Differences with Log Transformation

+

The introduction of util_doubledifflog_ts() appears to take things a step further by combining double differences and log transformation. This function seems poised to provide a comprehensive solution for users dealing with complex time series data. This is a helper function for auto_stationarize().

+
+
+
+

Minor Fixes and Improvements: Polishing the Experience

+
+

1. Attributes Enhancement in ts_growth_rate_vec()

+

The attention to detail is evident with the addition of attributes to the output of ts_growth_rate_vec(). This enhancement not only improves the clarity of results but also contributes to a more informative and user-friendly experience.

+
+
+

2. Refinement of auto_stationarize() in Response to User Feedback

+

Updates to auto_stationarize() based on user feedback (Fix #481 #483) demonstrate a commitment to refining existing features. This responsiveness to the community’s needs is commendable and ensures that the package evolves in sync with user expectations. It has taken all of the util_ transforms mentioned above in order to improve it’s functionality.

+
+
+

3. Integration with auto_arima Engine in ts_auto_arima()

+

The integration of ts_auto_arima() with the parsnip engine of auto_arima is a notable improvement. This update, triggered when .tune is set to FALSE, aligns the package with cutting-edge tools, potentially enhancing the efficiency and accuracy of time series modeling.

+

In conclusion, the release of healthyR.ts version 0.3.0 is an exciting leap forward. The new features introduce powerful capabilities, while the minor fixes and improvements showcase a commitment to providing a robust and user-friendly package. Users can look forward to a more versatile and refined experience in time series analysis. Great job on this release, and I’m sure the community is eager to explore these enhancements!

+
+
+
+

Examples

+

Let’s see how the main functions now behave.

+
+

auto_stationarize()

+
+
library(healthyR.ts)
+
+auto_stationarize(AirPassengers)
+
+
The time series is already stationary via ts_adf_test().
+
+
+
     Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
+1949 112 118 132 129 121 135 148 148 136 119 104 118
+1950 115 126 141 135 125 149 170 170 158 133 114 140
+1951 145 150 178 163 172 178 199 199 184 162 146 166
+1952 171 180 193 181 183 218 230 242 209 191 172 194
+1953 196 196 236 235 229 243 264 272 237 211 180 201
+1954 204 188 235 227 234 264 302 293 259 229 203 229
+1955 242 233 267 269 270 315 364 347 312 274 237 278
+1956 284 277 317 313 318 374 413 405 355 306 271 306
+1957 315 301 356 348 355 422 465 467 404 347 305 336
+1958 340 318 362 348 363 435 491 505 404 359 310 337
+1959 360 342 406 396 420 472 548 559 463 407 362 405
+1960 417 391 419 461 472 535 622 606 508 461 390 432
+
+
auto_stationarize(BJsales)
+
+
The time series is not stationary. Attempting to make it stationary...
+
+
+
$stationary_ts
+Time Series:
+Start = 3 
+End = 150 
+Frequency = 1 
+  [1]  0.5 -0.4  0.6  1.1 -2.8  3.0 -1.1  0.6 -0.5 -0.5  0.1  2.0 -0.6  0.8  1.2
+ [16] -3.4 -0.7 -0.3  1.7  3.0 -3.2  0.9  2.2 -2.5 -0.4  2.6 -4.3  2.0 -3.1  2.7
+ [31] -2.1  0.1  2.1 -0.2 -2.2  0.6  1.0 -2.6  3.0  0.3  0.2 -0.8  1.0  0.0  3.2
+ [46] -2.2 -4.7  1.2  0.8 -0.6 -0.4  0.6  1.0 -1.6 -0.1  3.4 -0.9 -1.7 -0.5  0.8
+ [61]  2.4 -1.9  0.6 -2.2  2.6 -0.1 -2.7  1.7 -0.3  1.9 -2.7  1.1 -0.6  0.9  0.0
+ [76]  1.8 -0.5 -0.4 -1.2  2.6 -1.8  1.7 -0.9  0.6 -0.4  3.0 -2.8  3.1 -2.3 -1.1
+ [91]  2.1 -0.3 -1.7 -0.8 -0.4  1.1 -1.5  0.3  1.4 -2.0  1.3 -0.3  0.4 -3.5  1.1
+[106]  2.6  0.4 -1.3  2.0 -1.6  0.6 -0.1 -1.4  1.6  1.6 -3.4  1.7 -2.2  2.1 -2.0
+[121] -0.2  0.2  0.7 -1.4  1.8 -0.1 -0.7  0.4  0.4  1.0 -2.4  1.0 -0.4  0.8 -1.0
+[136]  1.4 -1.2  1.1 -0.9  0.5  1.9 -0.6  0.3 -1.4 -0.9 -0.5  1.4  0.1
+
+$ndiffs
+[1] 1
+
+$adf_stats
+$adf_stats$test_stat
+[1] -6.562008
+
+$adf_stats$p_value
+[1] 0.01
+
+
+$trans_type
+[1] "double_diff"
+
+$ret
+[1] TRUE
+
+
plot.ts(auto_stationarize(BJsales)$stationary_ts)
+
+
The time series is not stationary. Attempting to make it stationary...
+
+
+

+
+
auto_stationarize(BJsales.lead)
+
+
The time series is not stationary. Attempting to make it stationary...
+
+
+
$stationary_ts
+Time Series:
+Start = 2 
+End = 150 
+Frequency = 1 
+  [1]  0.06  0.25 -0.57  0.58 -0.20  0.23 -0.04 -0.19  0.03  0.42  0.04  0.24
+ [13]  0.34 -0.46 -0.18 -0.08  0.29  0.56 -0.37  0.20  0.54 -0.31  0.03  0.52
+ [25] -0.70  0.35 -0.63  0.44 -0.38 -0.01  0.22  0.10 -0.50  0.01  0.30 -0.76
+ [37]  0.52  0.15  0.06 -0.10  0.21 -0.01  0.70 -0.22 -0.76  0.06  0.02 -0.17
+ [49] -0.08  0.01  0.11 -0.39  0.01  0.50 -0.02 -0.37 -0.13  0.05  0.54 -0.46
+ [61]  0.25 -0.52  0.44  0.02 -0.47  0.11  0.06  0.25 -0.35  0.00 -0.06  0.21
+ [73] -0.09  0.36  0.09 -0.04 -0.20  0.44 -0.23  0.40 -0.01  0.17  0.08  0.58
+ [85] -0.27  0.79 -0.21  0.02  0.30  0.28 -0.27 -0.01  0.03  0.16 -0.28  0.15
+ [97]  0.26 -0.36  0.32 -0.11  0.22 -0.65  0.00  0.47  0.16 -0.19  0.48 -0.26
+[109]  0.21  0.00 -0.20  0.35  0.38 -0.48  0.20 -0.32  0.43 -0.50  0.12 -0.17
+[121]  0.15 -0.36  0.35 -0.03 -0.18  0.16  0.07  0.21 -0.50  0.23 -0.13  0.14
+[133] -0.15  0.19 -0.24  0.26 -0.22  0.17  0.37 -0.06  0.29 -0.34 -0.12 -0.16
+[145]  0.25  0.08 -0.07  0.26 -0.37
+
+$ndiffs
+[1] 1
+
+$adf_stats
+$adf_stats$test_stat
+[1] -4.838625
+
+$adf_stats$p_value
+[1] 0.01
+
+
+$trans_type
+[1] "diff"
+
+$ret
+[1] TRUE
+
+
plot.ts(auto_stationarize(BJsales.lead)$stationary_ts)
+
+
The time series is not stationary. Attempting to make it stationary...
+
+
+

+
+
+
+
+

ts_auto_arima()

+

This use to only use the Arima engine if the .tune parameter was set to FALSE, thus it would many times give a simple straight line forecast. This was changed to make the engine auto_arima if .tune is set to FALSE.

+
+
library(timetk)
+library(dplyr)
+library(modeltime)
+
+data <- AirPassengers |>
+  ts_to_tbl() |>
+  select(-index)
+
+splits <- time_series_split(
+  data
+  , date_col
+  , assess = 12
+  , skip = 3
+  , cumulative = TRUE
+)
+
+ts_aa <- ts_auto_arima(
+  .data = data,
+  .num_cores = 2,
+  .date_col = date_col,
+  .value_col = value,
+  .rsamp_obj = splits,
+  .formula = value ~ .,
+  .grid_size = 5,
+  .cv_slice_limit = 2,
+  .tune = FALSE
+)
+
+ts_aa$recipe_info
+
+
$recipe_call
+recipe(.data = data, .date_col = date_col, .value_col = value, 
+    .formula = value ~ ., .rsamp_obj = splits, .tune = FALSE, 
+    .grid_size = 5, .num_cores = 2, .cv_slice_limit = 2)
+
+$recipe_syntax
+[1] "ts_arima_recipe <-"                                                                                                                                                                           
+[2] "\n  recipe(.data = data, .date_col = date_col, .value_col = value, .formula = value ~ \n    ., .rsamp_obj = splits, .tune = FALSE, .grid_size = 5, .num_cores = 2, \n    .cv_slice_limit = 2)"
+
+$rec_obj
+
+
ts_aa$model_info
+
+
$model_spec
+ARIMA Regression Model Specification (regression)
+
+Computational engine: auto_arima 
+
+
+$wflw
+══ Workflow ════════════════════════════════════════════════════════════════════
+Preprocessor: Recipe
+Model: arima_reg()
+
+── Preprocessor ────────────────────────────────────────────────────────────────
+0 Recipe Steps
+
+── Model ───────────────────────────────────────────────────────────────────────
+ARIMA Regression Model Specification (regression)
+
+Computational engine: auto_arima 
+
+
+$fitted_wflw
+══ Workflow [trained] ══════════════════════════════════════════════════════════
+Preprocessor: Recipe
+Model: arima_reg()
+
+── Preprocessor ────────────────────────────────────────────────────────────────
+0 Recipe Steps
+
+── Model ───────────────────────────────────────────────────────────────────────
+Series: outcome 
+ARIMA(1,1,0)(0,1,0)[12] 
+
+Coefficients:
+          ar1
+      -0.2431
+s.e.   0.0894
+
+sigma^2 = 109.8:  log likelihood = -447.95
+AIC=899.9   AICc=900.01   BIC=905.46
+
+$was_tuned
+[1] "not_tuned"
+
+
ts_aa$model_calibration
+
+
$plot
+
+$calibration_tbl
+# Modeltime Table
+# A tibble: 1 × 5
+  .model_id .model     .model_desc             .type .calibration_data
+      <int> <list>     <chr>                   <chr> <list>           
+1         1 <workflow> ARIMA(1,1,0)(0,1,0)[12] Test  <tibble [12 × 4]>
+
+$model_accuracy
+# A tibble: 1 × 9
+  .model_id .model_desc             .type   mae  mape  mase smape  rmse   rsq
+      <int> <chr>                   <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
+1         1 ARIMA(1,1,0)(0,1,0)[12] Test   18.5  4.18 0.384  4.03  23.9 0.955
+
+
ts_aa$model_calibration$plot
+
+
+ +
+
+

Finally enhancement to add attributes to ts_growth_rate_vec()

+
+
ts_growth_rate_vec(AirPassengers)
+
+
  [1]          NA   5.3571429  11.8644068  -2.2727273  -6.2015504  11.5702479
+  [7]   9.6296296   0.0000000  -8.1081081 -12.5000000 -12.6050420  13.4615385
+ [13]  -2.5423729   9.5652174  11.9047619  -4.2553191  -7.4074074  19.2000000
+ [19]  14.0939597   0.0000000  -7.0588235 -15.8227848 -14.2857143  22.8070175
+ [25]   3.5714286   3.4482759  18.6666667  -8.4269663   5.5214724   3.4883721
+ [31]  11.7977528   0.0000000  -7.5376884 -11.9565217  -9.8765432  13.6986301
+ [37]   3.0120482   5.2631579   7.2222222  -6.2176166   1.1049724  19.1256831
+ [43]   5.5045872   5.2173913 -13.6363636  -8.6124402  -9.9476440  12.7906977
+ [49]   1.0309278   0.0000000  20.4081633  -0.4237288  -2.5531915   6.1135371
+ [55]   8.6419753   3.0303030 -12.8676471 -10.9704641 -14.6919431  11.6666667
+ [61]   1.4925373  -7.8431373  25.0000000  -3.4042553   3.0837004  12.8205128
+ [67]  14.3939394  -2.9801325 -11.6040956 -11.5830116 -11.3537118  12.8078818
+ [73]   5.6768559  -3.7190083  14.5922747   0.7490637   0.3717472  16.6666667
+ [79]  15.5555556  -4.6703297 -10.0864553 -12.1794872 -13.5036496  17.2995781
+ [85]   2.1582734  -2.4647887  14.4404332  -1.2618297   1.5974441  17.6100629
+ [91]  10.4278075  -1.9370460 -12.3456790 -13.8028169 -11.4379085  12.9151292
+ [97]   2.9411765  -4.4444444  18.2724252  -2.2471910   2.0114943  18.8732394
+[103]  10.1895735   0.4301075 -13.4903640 -14.1089109 -12.1037464  10.1639344
+[109]   1.1904762  -6.4705882  13.8364780  -3.8674033   4.3103448  19.8347107
+[115]  12.8735632   2.8513238 -20.0000000 -11.1386139 -13.6490251   8.7096774
+[121]   6.8249258  -5.0000000  18.7134503  -2.4630542   6.0606061  12.3809524
+[127]  16.1016949   2.0072993 -17.1735242 -12.0950324 -11.0565111  11.8784530
+[133]   2.9629630  -6.2350120   7.1611253  10.0238663   2.3861171  13.3474576
+[139]  16.2616822  -2.5723473 -16.1716172  -9.2519685 -15.4013015  10.7692308
+attr(,"vector_attributes")
+attr(,"vector_attributes")$tsp
+[1] 1949.000 1960.917   12.000
+
+attr(,"vector_attributes")$class
+[1] "ts"
+
+attr(,"name")
+[1] "AirPassengers"
+
+
+ + +
+
+ +
+ +
+ + + + \ No newline at end of file diff --git a/docs/posts/2011-11-16/index_files/figure-html/unnamed-chunk-1-1.png b/docs/posts/2011-11-16/index_files/figure-html/unnamed-chunk-1-1.png new file mode 100644 index 0000000000000000000000000000000000000000..7d365804eb63e366243db9f8394baa07c94442b7 GIT binary patch literal 19402 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 GO&ol>RkzEMswnL}xX|Q>55o5-Wsu92BAd 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ǎ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 Hl+XkKW1Z&= literal 0 HcmV?d00001 diff --git a/docs/search.json b/docs/search.json index 956ac62f..4279c73f 100644 --- a/docs/search.json +++ b/docs/search.json @@ -11,7 +11,7 @@ "href": "index.html", "title": "Steve On Data", "section": "", - "text": "How to Perform Multiple Linear Regression in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nNov 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Predict a Single Value Using a Regression Model in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nNov 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnlocking the Power of Prediction Intervals in R: A Practical Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nNov 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Simulate & Plot a Bivariate Normal Distribution in R: A Hands-on Guide\n\n\n\n\n\n\n\nrtip\n\n\ndistribution\n\n\n\n\n\n\n\n\n\n\n\nNov 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDemystifying Data: A Comprehensive Guide to Calculating and Plotting Cumulative Distribution Functions (CDFs) in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nNov 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nIntroducing TidyDensity’s New Powerhouse: The convert_to_ts() Function\n\n\n\n\n\n\n\nrtip\n\n\ntidydensity\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nNov 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nFitting a Distribution to Data in R\n\n\n\n\n\n\n\nrtip\n\n\ndistribution\n\n\n\n\n\n\n\n\n\n\n\nNov 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnderstanding the Triangular Distribution and Its Application in R\n\n\n\n\n\n\n\nrtip\n\n\ndistribution\n\n\n\n\n\n\n\n\n\n\n\nNov 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMultinomial Distribution in R\n\n\n\n\n\n\n\nrtip\n\n\ndistribution\n\n\n\n\n\n\n\n\n\n\n\nOct 31, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nRandomness in R: runif(), punif(), dunif(), and quinf()\n\n\n\n\n\n\n\nrtip\n\n\ndistribution\n\n\n\n\n\n\n\n\n\n\n\nOct 30, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPlotting Log Log Plots In Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPlotting a Logistic Regression In Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWhat’s a Bland-Altman Plot? In Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating a Scree Plot in Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Create a Bubble Chart in R using ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\nggplot2\n\n\n\n\n\n\n\n\n\n\n\nOct 23, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Pareto Charts in R with the qcc Package\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Interaction Plots in R: Unveiling Hidden Relationships\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMaking Time Series Stationary Made Easy with auto_stationarize()\n\n\n\n\n\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nOct 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nTesting stationarity with the ts_adf_test() function in R\n\n\n\n\n\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nOct 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAnalyzing Time Series Growth with ts_growth_rate_vec() in healthyR.ts\n\n\n\n\n\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nOct 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering the Art of Drawing Circles in Plots with R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Use cex to Change the Size of Plot Elements in base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 12, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHorizontal Legends in Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nResizing Legends in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Legends in R: Drawing Them Outside the Plot\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nIntroduction\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 5, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Stacked Dot Plots in R: A Guide with Base R and ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 4, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Interactive Radar Charts in R with the ‘fmsb’ Library\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHorizontal Boxplots in R using the Palmer Penguins Data Set\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPlotting Decision Trees in R with rpart and rpart.plot\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 29, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Reorder Boxplots in R: A Comprehensive Guide\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nEnhancing Your Data Visualizations with Base R: Overlaying Points and Lines\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Visualization with ggplot2: A Guide to Using facet_grid()\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\nggplot2\n\n\n\n\n\n\n\n\n\n\n\nSep 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Visualization with Pairs Plots in Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Confidence Intervals for a Linear Model in R Using Base R and the Iris Dataset\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Visualization in R: Plotting Predicted Values with the mtcars Dataset\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Data with Scatter Plots by Group in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Histogram Breaks in R: Unveiling the Power of Data Visualization\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHistograms with Two or More Variables in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Create a Histogram with Different Colors in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Plot Multiple Plots on the Same Graph in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring the Third Dimension with R: A Guide to the persp() Function\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 12, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPlotting SVM Decision Boundaries with e1071 in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Population Pyramid Plots in R with ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Visualization in R: How to Plot a Subset of Data\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 7, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Multivariate Data with Principal Component Analysis (PCA) Biplot in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWhen to use Jitter\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 5, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nKernel Density Plots in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Eye-Catching Data Visualizations with Lollipop Charts in R using ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 31, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Relationships with Correlation Heatmaps in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\ncorrelation\n\n\n\n\n\n\n\n\n\n\n\nAug 30, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nVisualizing Categorical Data in R: A Guide with Engaging Charts Using the Iris Dataset\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 29, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nEnhancing Your Histograms in R: Adding Vertical Lines for Better Insights\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Plot Multiple Histograms with Base R and ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPlotting Multiple Lines on a Graph in R: A Step-by-Step Guide\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Data Distribution in R: A Comprehensive Guide\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 23, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnveiling Data Distribution Patterns with stripchart() in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Box Plots with Mean Values using Base R and ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 21, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Data Distribution with Box Plots in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Approximation with R’s approx() Function\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring the Power of the curve() Function in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSolving Systems of Equations in R using the solve() Function\n\n\n\n\n\n\n\nrtip\n\n\nlinearequations\n\n\n\n\n\n\n\n\n\n\n\nAug 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe substring() function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\npmax() and pmin(): Finding the Parallel Maximum and Minimum in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Grouped Counting in R: A Comprehensive Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Visualization: A Guide to Harnessing the Power of R’s par() Function\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 9, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Transformation with the scale() Function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nEnhance Your Plots with the text() Function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 7, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring R’s Versatile str() Function: Unraveling Your Data with Ease!\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 4, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA Handy Guide to read.delim() in R - Unraveling the Magic of Reading Tabular Data\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe unlist() Function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nR Functions for Getting Objects\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe replicate() function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 31, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe intersect() function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnleashing the Power of Cumulative Mean in R: A Step-by-Step Guide\n\n\n\n\n\n\n\nrtip\n\n\ncumulative\n\n\n\n\n\n\n\n\n\n\n\nJul 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSummarizing Data in R: tapply() vs. group_by() and summarize()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnraveling Data Insights with R’s fivenum(): A Programmer’s Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Calculate Percentage by Group in R using Base R, dplyr, and data.table\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHarness the Power of paste() and cat() in R: Combining and Displaying Text Like a Pro\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 21, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplify Your Code with R’s Powerful Functions: with() and within()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to subset list objects in R\n\n\n\n\n\n\n\nrtip\n\n\nlist\n\n\nsubset\n\n\n\n\n\n\n\n\n\n\n\nJul 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nEfficiently Finding Duplicate Rows in R: A Comparative Analysis\n\n\n\n\n\n\n\nrtip\n\n\nbenchmark\n\n\ndplyr\n\n\ndatatable\n\n\n\n\n\n\n\n\n\n\n\nJul 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nFinding Duplicate Values in a Data Frame in R: A Guide Using Base R and dplyr\n\n\n\n\n\n\n\nrtip\n\n\nbenchmark\n\n\n\n\n\n\n\n\n\n\n\nJul 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCovariance in R with the cov() Function\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying File Existence Checking in R with file.exists()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Data with colMeans() in R: A Programmer’s Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 12, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA Closer Look at the R Function identical()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying File Management in R: Introducing file.rename()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 30, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Use a Windows .bat File to Execute an R Script\n\n\n\n\n\n\n\nrtip\n\n\nbatchfile\n\n\n\n\n\n\n\n\n\n\n\nJun 29, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Rolling Correlation with the rollapply Function: A Powerful Tool for Analyzing Time-Series Data\n\n\n\n\n\n\n\nrtip\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nJun 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe ave() Function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nVisualization in R: Unleashing the Power of the abline() Function\n\n\n\n\n\n\n\nrtip\n\n\nabline\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nJun 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBootstrap Function in R: Resampling with the lapply and sample Functions\n\n\n\n\n\n\n\nrtip\n\n\nbootstrap\n\n\nlapply\n\n\nsample\n\n\n\n\n\n\n\n\n\n\n\nJun 23, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Repetition with R’s rep() Function: A Programmer’s Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnleashing the Power of Sampling in R: Exploring the Versatile sample() Function\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 21, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Aggregation with xtabs() in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering the Power of R’s diff() Function: A Programmer’s Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nIntroduction to Linear Regression in R: Analyzing the mtcars Dataset with lm()\n\n\n\n\n\n\n\nrtip\n\n\nlinear\n\n\nregression\n\n\n\n\n\n\n\n\n\n\n\nJun 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPulling a formula from a recipe object\n\n\n\n\n\n\n\nrtip\n\n\nrecipes\n\n\ntidymodels\n\n\n\n\n\n\n\n\n\n\n\nJun 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying Model Formulas with the R Function ‘reformulate()’\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnderstanding the file.info() Function in R: Listing Files by Date\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying Data Transformation with pivot_longer() in R’s tidyr Library\n\n\n\n\n\n\n\nrtip\n\n\ntidyr\n\n\n\n\n\n\n\n\n\n\n\nJun 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSorting, Ordering, and Ranking: Unraveling R’s Powerful Functions\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe do.call() function in R: Unlocking Efficiency and Flexibility\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDemystifying Regular Expressions: A Programmer’s Guide for Beginners\n\n\n\n\n\n\n\nrtip\n\n\nregex\n\n\n\n\n\n\n\n\n\n\n\nMay 31, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying Logical Operations with the R Function any()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nMay 30, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWhy Check File Size Output for Different Methods?\n\n\n\n\n\n\n\nrtip\n\n\nexcel\n\n\nopenxlsx\n\n\nxlsx\n\n\nwritexl\n\n\n\n\n\n\n\n\n\n\n\nMay 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nComparing R Packages for Writing Excel Files: An Analysis of writexl, openxlsx, and xlsx in R\n\n\n\n\n\n\n\nrtip\n\n\nexcel\n\n\nopenxlsx\n\n\nxlsx\n\n\nwritexl\n\n\n\n\n\n\n\n\n\n\n\nMay 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Data with TidyDensity: A Guide to Using tidy_empirical() and tidy_four_autoplot() in R\n\n\n\n\n\n\n\nrtip\n\n\ntidydensity\n\n\ndplyr\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nMay 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWhat is the sink() function? Capturing Output to External Files\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nMay 23, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUpdate to {TidyDensity}\n\n\n\n\n\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nMay 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering File Manipulation with R’s list.files() Function\n\n\n\n\n\n\n\nrtip\n\n\nfiles\n\n\n\n\n\n\n\n\n\n\n\nMay 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe which() Function in R\n\n\n\n\n\n\n\nrtip\n\n\nwhich\n\n\n\n\n\n\n\n\n\n\n\nMay 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWorking with Dates and Times Pt 4\n\n\n\n\n\n\n\nrtip\n\n\ndatetime\n\n\n\n\n\n\n\n\n\n\n\nMay 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWorking with Dates and Times Pt 3\n\n\n\n\n\n\n\nrtip\n\n\ndatetime\n\n\n\n\n\n\n\n\n\n\n\nMay 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWorking with Dates and Times Pt 2: Finding the Next Mothers Day with Simplicity\n\n\n\n\n\n\n\nrtip\n\n\ndatetime\n\n\n\n\n\n\n\n\n\n\n\nMay 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWorking with Dates and Times Pt 1\n\n\n\n\n\n\n\nrtip\n\n\ndatetime\n\n\n\n\n\n\n\n\n\n\n\nMay 12, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nVBA to R and Back Again: Running R from VBA Pt 2\n\n\n\n\n\n\n\nrtip\n\n\nvba\n\n\nexcel\n\n\n\n\n\n\n\n\n\n\n\nMay 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nVBA to R and Back Again: Running R from VBA\n\n\n\n\n\n\n\nrtip\n\n\nvba\n\n\nexcel\n\n\n\n\n\n\n\n\n\n\n\nMay 9, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUpdates to {healthyR.data}\n\n\n\n\n\n\n\nrtip\n\n\nhealthyrdata\n\n\n\n\n\n\n\n\n\n\n\nMay 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMaps with {shiny} Pt 2\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\nmapping\n\n\n\n\n\n\n\n\n\n\n\nMay 5, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMaps with {shiny}\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\nmapping\n\n\n\n\n\n\n\n\n\n\n\nMay 4, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Download a File from the Internet using download.file()\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\nreadxl\n\n\nexcel\n\n\n\n\n\n\n\n\n\n\n\nMay 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExtracting a model call from a fitted workflow in {tidymodels}\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidymodels\n\n\n\n\n\n\n\n\n\n\n\nMay 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBuilding models with {shiny} and {tidyAML} Part 4\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidymodels\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nApr 29, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBuilding models with {shiny} and {tidyAML} Part 3\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidymodels\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nApr 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBuilding models with {shiny} and {tidyAML} Part 2\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidymodels\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nApr 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBuilding models with {shiny} and {tidyAML} Part 1\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidymodels\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nApr 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Distributions with {shiny}, {TidyDensity} and {plotly} Part 5\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\nplotly\n\n\n\n\n\n\n\n\n\n\n\nApr 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Distributions with {shiny} and {TidyDensity} Part 4\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nApr 21, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Distributions with {shiny} and {TidyDensity} Part 3\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nApr 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Distributions with {shiny} and {TidyDensity} Part 2\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nApr 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Distributions with {shiny} and {TidyDensity}\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nApr 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nStyling Tables for Excel with {styledTables}\n\n\n\n\n\n\n\nrtip\n\n\nexcel\n\n\n\n\n\n\n\n\n\n\n\nApr 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nReading in Multiple Excel Sheets with lapply and {readxl}\n\n\n\n\n\n\n\nrtip\n\n\nreadxl\n\n\nlapply\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nApr 7, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA New Package for the African Stock Market {BRVM}\n\n\n\n\n\n\n\nrtip\n\n\nbrvm\n\n\nmarkets\n\n\n\n\n\n\n\n\n\n\n\nApr 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nLooking at Daily Log Returns with tidyquant, TidyDensity, and Shiny\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\ntidyquant\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nApr 5, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA sample Shiny App to view Forecasts on the AirPassengers Data\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ndata\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nApr 4, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA sample Shiny App to view CMS Healthcare Data\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ndata\n\n\nhealthcare\n\n\ncms\n\n\n\n\n\n\n\n\n\n\n\nApr 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA Bootstrapped Time Series Model with auto.arima() from {forecast}\n\n\n\n\n\n\n\nrtip\n\n\ntimeseries\n\n\nbootstrap\n\n\n\n\n\n\n\n\n\n\n\nMar 29, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow fast does a compressed file in Part 2\n\n\n\n\n\n\n\nrtip\n\n\nbenchmark\n\n\narrow\n\n\nduckdb\n\n\ndatatable\n\n\nreadr\n\n\n\n\n\n\n\n\n\n\n\nMar 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow fast does a compressed file in?\n\n\n\n\n\n\n\nrtip\n\n\nbenchmark\n\n\n\n\n\n\n\n\n\n\n\nMar 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow fast do the files read in?\n\n\n\n\n\n\n\nrtip\n\n\nbenchmark\n\n\n\n\n\n\n\n\n\n\n\nMar 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSome Examples of Cumulative Mean with {TidyDensity}\n\n\n\n\n\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nMar 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGetting the CCI30 Index Current Makeup\n\n\n\n\n\n\n\ncrypto\n\n\ncci30\n\n\n\n\n\n\n\n\n\n\n\nMar 21, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUse of the apply family of functions\n\n\n\n\n\n\n\nthanks\n\n\n\n\n\n\n\n\n\n\n\nMar 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUse of the apply family of functions\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\napply\n\n\n\n\n\n\n\n\n\n\n\nMar 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMultiple Solutions to speedup tidy_bernoulli() with {data.table}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\ndatatable\n\n\n\n\n\n\n\n\n\n\n\nMar 9, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGetting NYS Home Heating Oil Prices with {rvest}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nrvest\n\n\n\n\n\n\n\n\n\n\n\nMar 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\ntidy_bernoulli() with {data.table}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ndatatable\n\n\n\n\n\n\n\n\n\n\n\nMar 7, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimple examples of imap() from {purrr}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nMar 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimple examples of pmap() from {purrr}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nMar 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nForecasting Timeseries in a list with R\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nMar 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nText Processing Made Easy with {healthyR}’s sql_left(), sql_mid(), and sql_right() Functions in R\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nsql\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nMar 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nOpen a File Folder in R\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nshell\n\n\n\n\n\n\n\n\n\n\n\nFeb 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nQuickly Generate Nested Time Series Models\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nautoarima\n\n\n\n\n\n\n\n\n\n\n\nFeb 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nData Preppers with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\npreprocessor\n\n\n\n\n\n\n\n\n\n\n\nFeb 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCalibrate and Plot a Time Series with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nFeb 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nConverting a {tidyAML} tibble to a {workflowsets}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\nworkflowsets\n\n\ntidymodels\n\n\n\n\n\n\n\n\n\n\n\nFeb 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nOfficially on CRAN {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nFeb 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMoving Average Plots with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nFeb 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAn example of using {box}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nbox\n\n\n\n\n\n\n\n\n\n\n\nFeb 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nOff to CRAN! {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\ntidymodels\n\n\n\n\n\n\n\n\n\n\n\nFeb 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGet the Current Hospital Data Set from CMS with {healthyR.data}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrdata\n\n\n\n\n\n\n\n\n\n\n\nFeb 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating and Predicting Fast Regression Parsnip Models with {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nFeb 9, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating an R Project Directory\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nFeb 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSubsetting Named Lists in R\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlist\n\n\n\n\n\n\n\n\n\n\n\nFeb 7, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCumulative Measurement Functions with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nFeb 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe Argument Matcher: A Function for Selecting the Right Arguments {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlist\n\n\ntidyaml\n\n\ntidymodels\n\n\n\n\n\n\n\n\n\n\n\nFeb 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDiverging Lollipop Chart: A Visual Tool for Comparing Data with {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyr\n\n\nplots\n\n\n\n\n\n\n\n\n\n\n\nFeb 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAttributes in R Functions: An Overview\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nmetadata\n\n\nattributes\n\n\n\n\n\n\n\n\n\n\n\nFeb 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMedian: A Simple Way to Detect Excess Events Over Time with {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nJan 31, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\n{healthyR.ts}: The New and Improved Library for Time Series Analysis\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nJan 30, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nService Line Grouping with {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\naugment\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nJan 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nTransforming Your Data: A Guide to Popular Methods and How to Implement Them with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntransforms\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nJan 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying List Filtering in R with purrr’s keep()\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlists\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nJan 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMaking Non Stationary Data Stationary\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlists\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nJan 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nADF and Phillips-Perron Tests for Stationarity using lists\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlists\n\n\ntimeseries\n\n\nlapply\n\n\n\n\n\n\n\n\n\n\n\nJan 23, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAnother Post on Lists\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlists\n\n\n\n\n\n\n\n\n\n\n\nJan 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBoilerplate XGBoost with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nxgboost\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nJan 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGeometric Brownian Motion with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nJan 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAugmenting a Brownian Motion to a Time Series with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nJan 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAuto K-Means with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nkmeans\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nJan 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe building of {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nJan 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAn Update on {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\nautoml\n\n\n\n\n\n\n\n\n\n\n\nJan 12, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nReflecting on the Past Year: A LinkedIn Year in Review\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlinkedin\n\n\n\n\n\n\n\n\n\n\n\nJan 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nOptimal Break Points for Histograms with {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyr\n\n\nhistograms\n\n\n\n\n\n\n\n\n\n\n\nJan 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nNew Release of {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nJan 9, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBrownian Motion\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nweeklytip\n\n\n\n\n\n\n\n\n\n\n\nJan 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMore Randomwalks with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\nrandomwalk\n\n\n\n\n\n\n\n\n\n\n\nJan 5, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCalendar Heatmap with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nJan 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nEvent Analysis with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nDec 30, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGartner Magic Chart and its usefulness in healthcare analytics with {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nDec 29, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimulating Time Series Model Forecasts with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\nhealthyrts\n\n\ntimeseries\n\n\nsimulation\n\n\n\n\n\n\n\n\n\n\n\nDec 23, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nListing Functions and Parameters\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ndplyr\n\n\n\n\n\n\n\n\n\n\n\nDec 22, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDistribution Statistics with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nDec 21, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nRandom Walks with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nrandomwalk\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nDec 20, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nViewing Different Versions of the Same Statistical Distribution with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ndistributions\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nDec 19, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nModel Scedacity Plots with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nDec 16, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimple Moving Average Plots with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nDec 15, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDistribution Summaries with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nDec 14, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMixture Distributions with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\nmixturemodels\n\n\n\n\n\n\n\n\n\n\n\nDec 13, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreate QQ Plots for Time Series Models with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nDec 9, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreate a Faceted Historgram Plot with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhistograms\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nDec 8, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreate Multiple {parsnip} Model Specs with {purrr}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nparsnip\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nDec 7, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nZ-Score Scaling Step Recipe with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nrecipes\n\n\n\n\n\n\n\n\n\n\n\nDec 6, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nNaming Items in a List with {purrr}, {dplyr}, or {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nDec 5, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAuto KNN with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\nknn\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nDec 2, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExtract Boilerplate Workflow Metrics with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nDec 1, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGenerate Random Walk Data with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nNov 30, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWorking with Lists\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlists\n\n\nlapply\n\n\n\n\n\n\n\n\n\n\n\nNov 29, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDefault Metric Sets with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nNov 28, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSummary Statistics with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\ntidydensity\n\n\ndatatable\n\n\n\n\n\n\n\n\n\n\n\nNov 23, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nData Preprocessing Scale/Normalize with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nrecipes\n\n\n\n\n\n\n\n\n\n\n\nNov 22, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBootstrap Modeling with Base R\n\n\n\n\n\n\n\ncode\n\n\nbootstrap\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nNov 21, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUpdates to {healthyverse} packages\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\nhealthyverse\n\n\n\n\n\n\n\n\n\n\n\nNov 18, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBootstrap Modeling with {purrr} and {modler}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\nmodelr\n\n\n\n\n\n\n\n\n\n\n\nNov 17, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCumulative Harmonic Mean with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nNov 16, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAuto Prep data for XGBoost with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nxgboost\n\n\n\n\n\n\n\n\n\n\n\nNov 15, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nFind Skewed Features with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nskew\n\n\n\n\n\n\n\n\n\n\n\nNov 14, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nTime Series Lag Correlation Plots\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrts\n\n\nweeklytip\n\n\n\n\n\n\n\n\n\n\n\nNov 11, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nReading Multiple Files with {purrr}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nNov 10, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMapping K-Means with healthyR.ai\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nkmeans\n\n\n\n\n\n\n\n\n\n\n\nNov 9, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHyperbolic Transform with healthyR.ai\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nNov 8, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDiscrete Fourier Vec with healthyR.ai\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nNov 7, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBootstrapping and Plots with TidyDensity\n\n\n\n\n\n\n\ncode\n\n\ntidydensity\n\n\nbootstrap\n\n\nweeklytip\n\n\n\n\n\n\n\n\n\n\n\nNov 4, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCumulative Skewness\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nOct 31, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPCA with healthyR.ai\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nweeklytip\n\n\n\n\n\n\n\n\n\n\n\nOct 28, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nControl Charts in healthyR.ai\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nOct 26, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCumulative Variance\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ncumulative\n\n\nsapply\n\n\nlapply\n\n\n\n\n\n\n\n\n\n\n\nOct 24, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nTime Series Clustering with healthyR.ts\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nOct 21, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nhealthyR.ai Primer\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nOct 13, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nTidyDensity Primer\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nOct 7, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimple lapply()\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nweeklytip\n\n\n\n\n\n\n\n\n\n\n\nOct 5, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWelcome To Steve On Data\n\n\n\n\n\n\n\nnews\n\n\n\n\n\n\n\n\n\n\n\nOct 5, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\nNo matching items" + "text": "{healthyR.ts} New Features: Unlocking More Power\n\n\n\n\n\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nNov 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Perform Multiple Linear Regression in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nNov 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Predict a Single Value Using a Regression Model in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nNov 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnlocking the Power of Prediction Intervals in R: A Practical Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nNov 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Simulate & Plot a Bivariate Normal Distribution in R: A Hands-on Guide\n\n\n\n\n\n\n\nrtip\n\n\ndistribution\n\n\n\n\n\n\n\n\n\n\n\nNov 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDemystifying Data: A Comprehensive Guide to Calculating and Plotting Cumulative Distribution Functions (CDFs) in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nNov 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nIntroducing TidyDensity’s New Powerhouse: The convert_to_ts() Function\n\n\n\n\n\n\n\nrtip\n\n\ntidydensity\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nNov 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nFitting a Distribution to Data in R\n\n\n\n\n\n\n\nrtip\n\n\ndistribution\n\n\n\n\n\n\n\n\n\n\n\nNov 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnderstanding the Triangular Distribution and Its Application in R\n\n\n\n\n\n\n\nrtip\n\n\ndistribution\n\n\n\n\n\n\n\n\n\n\n\nNov 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMultinomial Distribution in R\n\n\n\n\n\n\n\nrtip\n\n\ndistribution\n\n\n\n\n\n\n\n\n\n\n\nOct 31, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nRandomness in R: runif(), punif(), dunif(), and quinf()\n\n\n\n\n\n\n\nrtip\n\n\ndistribution\n\n\n\n\n\n\n\n\n\n\n\nOct 30, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPlotting Log Log Plots In Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPlotting a Logistic Regression In Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWhat’s a Bland-Altman Plot? In Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating a Scree Plot in Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Create a Bubble Chart in R using ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\nggplot2\n\n\n\n\n\n\n\n\n\n\n\nOct 23, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Pareto Charts in R with the qcc Package\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Interaction Plots in R: Unveiling Hidden Relationships\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMaking Time Series Stationary Made Easy with auto_stationarize()\n\n\n\n\n\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nOct 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nTesting stationarity with the ts_adf_test() function in R\n\n\n\n\n\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nOct 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAnalyzing Time Series Growth with ts_growth_rate_vec() in healthyR.ts\n\n\n\n\n\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nOct 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering the Art of Drawing Circles in Plots with R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Use cex to Change the Size of Plot Elements in base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 12, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHorizontal Legends in Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nResizing Legends in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Legends in R: Drawing Them Outside the Plot\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nIntroduction\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 5, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Stacked Dot Plots in R: A Guide with Base R and ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 4, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Interactive Radar Charts in R with the ‘fmsb’ Library\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHorizontal Boxplots in R using the Palmer Penguins Data Set\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nOct 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPlotting Decision Trees in R with rpart and rpart.plot\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 29, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Reorder Boxplots in R: A Comprehensive Guide\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nEnhancing Your Data Visualizations with Base R: Overlaying Points and Lines\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Visualization with ggplot2: A Guide to Using facet_grid()\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\nggplot2\n\n\n\n\n\n\n\n\n\n\n\nSep 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Visualization with Pairs Plots in Base R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Confidence Intervals for a Linear Model in R Using Base R and the Iris Dataset\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Visualization in R: Plotting Predicted Values with the mtcars Dataset\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Data with Scatter Plots by Group in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Histogram Breaks in R: Unveiling the Power of Data Visualization\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHistograms with Two or More Variables in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Create a Histogram with Different Colors in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Plot Multiple Plots on the Same Graph in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring the Third Dimension with R: A Guide to the persp() Function\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 12, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPlotting SVM Decision Boundaries with e1071 in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Population Pyramid Plots in R with ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Visualization in R: How to Plot a Subset of Data\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 7, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Multivariate Data with Principal Component Analysis (PCA) Biplot in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWhen to use Jitter\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 5, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nKernel Density Plots in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nSep 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating Eye-Catching Data Visualizations with Lollipop Charts in R using ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 31, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Relationships with Correlation Heatmaps in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\ncorrelation\n\n\n\n\n\n\n\n\n\n\n\nAug 30, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nVisualizing Categorical Data in R: A Guide with Engaging Charts Using the Iris Dataset\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 29, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nEnhancing Your Histograms in R: Adding Vertical Lines for Better Insights\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Plot Multiple Histograms with Base R and ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPlotting Multiple Lines on a Graph in R: A Step-by-Step Guide\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Data Distribution in R: A Comprehensive Guide\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 23, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnveiling Data Distribution Patterns with stripchart() in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Box Plots with Mean Values using Base R and ggplot2\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 21, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Data Distribution with Box Plots in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Approximation with R’s approx() Function\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring the Power of the curve() Function in R\n\n\n\n\n\n\n\nrtip\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nAug 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSolving Systems of Equations in R using the solve() Function\n\n\n\n\n\n\n\nrtip\n\n\nlinearequations\n\n\n\n\n\n\n\n\n\n\n\nAug 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe substring() function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\npmax() and pmin(): Finding the Parallel Maximum and Minimum in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Grouped Counting in R: A Comprehensive Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Visualization: A Guide to Harnessing the Power of R’s par() Function\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 9, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Transformation with the scale() Function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nEnhance Your Plots with the text() Function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 7, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring R’s Versatile str() Function: Unraveling Your Data with Ease!\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 4, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA Handy Guide to read.delim() in R - Unraveling the Magic of Reading Tabular Data\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe unlist() Function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nR Functions for Getting Objects\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nAug 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe replicate() function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 31, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe intersect() function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnleashing the Power of Cumulative Mean in R: A Step-by-Step Guide\n\n\n\n\n\n\n\nrtip\n\n\ncumulative\n\n\n\n\n\n\n\n\n\n\n\nJul 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSummarizing Data in R: tapply() vs. group_by() and summarize()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnraveling Data Insights with R’s fivenum(): A Programmer’s Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Calculate Percentage by Group in R using Base R, dplyr, and data.table\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHarness the Power of paste() and cat() in R: Combining and Displaying Text Like a Pro\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 21, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplify Your Code with R’s Powerful Functions: with() and within()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to subset list objects in R\n\n\n\n\n\n\n\nrtip\n\n\nlist\n\n\nsubset\n\n\n\n\n\n\n\n\n\n\n\nJul 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nEfficiently Finding Duplicate Rows in R: A Comparative Analysis\n\n\n\n\n\n\n\nrtip\n\n\nbenchmark\n\n\ndplyr\n\n\ndatatable\n\n\n\n\n\n\n\n\n\n\n\nJul 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nFinding Duplicate Values in a Data Frame in R: A Guide Using Base R and dplyr\n\n\n\n\n\n\n\nrtip\n\n\nbenchmark\n\n\n\n\n\n\n\n\n\n\n\nJul 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCovariance in R with the cov() Function\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying File Existence Checking in R with file.exists()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Data with colMeans() in R: A Programmer’s Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 12, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA Closer Look at the R Function identical()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJul 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying File Management in R: Introducing file.rename()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 30, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Use a Windows .bat File to Execute an R Script\n\n\n\n\n\n\n\nrtip\n\n\nbatchfile\n\n\n\n\n\n\n\n\n\n\n\nJun 29, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Rolling Correlation with the rollapply Function: A Powerful Tool for Analyzing Time-Series Data\n\n\n\n\n\n\n\nrtip\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nJun 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe ave() Function in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nVisualization in R: Unleashing the Power of the abline() Function\n\n\n\n\n\n\n\nrtip\n\n\nabline\n\n\nviz\n\n\n\n\n\n\n\n\n\n\n\nJun 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBootstrap Function in R: Resampling with the lapply and sample Functions\n\n\n\n\n\n\n\nrtip\n\n\nbootstrap\n\n\nlapply\n\n\nsample\n\n\n\n\n\n\n\n\n\n\n\nJun 23, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Repetition with R’s rep() Function: A Programmer’s Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnleashing the Power of Sampling in R: Exploring the Versatile sample() Function\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 21, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering Data Aggregation with xtabs() in R\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering the Power of R’s diff() Function: A Programmer’s Guide\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nIntroduction to Linear Regression in R: Analyzing the mtcars Dataset with lm()\n\n\n\n\n\n\n\nrtip\n\n\nlinear\n\n\nregression\n\n\n\n\n\n\n\n\n\n\n\nJun 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPulling a formula from a recipe object\n\n\n\n\n\n\n\nrtip\n\n\nrecipes\n\n\ntidymodels\n\n\n\n\n\n\n\n\n\n\n\nJun 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying Model Formulas with the R Function ‘reformulate()’\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUnderstanding the file.info() Function in R: Listing Files by Date\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying Data Transformation with pivot_longer() in R’s tidyr Library\n\n\n\n\n\n\n\nrtip\n\n\ntidyr\n\n\n\n\n\n\n\n\n\n\n\nJun 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSorting, Ordering, and Ranking: Unraveling R’s Powerful Functions\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe do.call() function in R: Unlocking Efficiency and Flexibility\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nJun 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDemystifying Regular Expressions: A Programmer’s Guide for Beginners\n\n\n\n\n\n\n\nrtip\n\n\nregex\n\n\n\n\n\n\n\n\n\n\n\nMay 31, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying Logical Operations with the R Function any()\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nMay 30, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWhy Check File Size Output for Different Methods?\n\n\n\n\n\n\n\nrtip\n\n\nexcel\n\n\nopenxlsx\n\n\nxlsx\n\n\nwritexl\n\n\n\n\n\n\n\n\n\n\n\nMay 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nComparing R Packages for Writing Excel Files: An Analysis of writexl, openxlsx, and xlsx in R\n\n\n\n\n\n\n\nrtip\n\n\nexcel\n\n\nopenxlsx\n\n\nxlsx\n\n\nwritexl\n\n\n\n\n\n\n\n\n\n\n\nMay 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Data with TidyDensity: A Guide to Using tidy_empirical() and tidy_four_autoplot() in R\n\n\n\n\n\n\n\nrtip\n\n\ntidydensity\n\n\ndplyr\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nMay 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWhat is the sink() function? Capturing Output to External Files\n\n\n\n\n\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nMay 23, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUpdate to {TidyDensity}\n\n\n\n\n\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nMay 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMastering File Manipulation with R’s list.files() Function\n\n\n\n\n\n\n\nrtip\n\n\nfiles\n\n\n\n\n\n\n\n\n\n\n\nMay 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe which() Function in R\n\n\n\n\n\n\n\nrtip\n\n\nwhich\n\n\n\n\n\n\n\n\n\n\n\nMay 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWorking with Dates and Times Pt 4\n\n\n\n\n\n\n\nrtip\n\n\ndatetime\n\n\n\n\n\n\n\n\n\n\n\nMay 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWorking with Dates and Times Pt 3\n\n\n\n\n\n\n\nrtip\n\n\ndatetime\n\n\n\n\n\n\n\n\n\n\n\nMay 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWorking with Dates and Times Pt 2: Finding the Next Mothers Day with Simplicity\n\n\n\n\n\n\n\nrtip\n\n\ndatetime\n\n\n\n\n\n\n\n\n\n\n\nMay 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWorking with Dates and Times Pt 1\n\n\n\n\n\n\n\nrtip\n\n\ndatetime\n\n\n\n\n\n\n\n\n\n\n\nMay 12, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nVBA to R and Back Again: Running R from VBA Pt 2\n\n\n\n\n\n\n\nrtip\n\n\nvba\n\n\nexcel\n\n\n\n\n\n\n\n\n\n\n\nMay 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nVBA to R and Back Again: Running R from VBA\n\n\n\n\n\n\n\nrtip\n\n\nvba\n\n\nexcel\n\n\n\n\n\n\n\n\n\n\n\nMay 9, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUpdates to {healthyR.data}\n\n\n\n\n\n\n\nrtip\n\n\nhealthyrdata\n\n\n\n\n\n\n\n\n\n\n\nMay 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMaps with {shiny} Pt 2\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\nmapping\n\n\n\n\n\n\n\n\n\n\n\nMay 5, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMaps with {shiny}\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\nmapping\n\n\n\n\n\n\n\n\n\n\n\nMay 4, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow to Download a File from the Internet using download.file()\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\nreadxl\n\n\nexcel\n\n\n\n\n\n\n\n\n\n\n\nMay 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExtracting a model call from a fitted workflow in {tidymodels}\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidymodels\n\n\n\n\n\n\n\n\n\n\n\nMay 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBuilding models with {shiny} and {tidyAML} Part 4\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidymodels\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nApr 29, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBuilding models with {shiny} and {tidyAML} Part 3\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidymodels\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nApr 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBuilding models with {shiny} and {tidyAML} Part 2\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidymodels\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nApr 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBuilding models with {shiny} and {tidyAML} Part 1\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidymodels\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nApr 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Distributions with {shiny}, {TidyDensity} and {plotly} Part 5\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\nplotly\n\n\n\n\n\n\n\n\n\n\n\nApr 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Distributions with {shiny} and {TidyDensity} Part 4\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nApr 21, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Distributions with {shiny} and {TidyDensity} Part 3\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nApr 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Distributions with {shiny} and {TidyDensity} Part 2\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nApr 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExploring Distributions with {shiny} and {TidyDensity}\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nApr 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nStyling Tables for Excel with {styledTables}\n\n\n\n\n\n\n\nrtip\n\n\nexcel\n\n\n\n\n\n\n\n\n\n\n\nApr 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nReading in Multiple Excel Sheets with lapply and {readxl}\n\n\n\n\n\n\n\nrtip\n\n\nreadxl\n\n\nlapply\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nApr 7, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA New Package for the African Stock Market {BRVM}\n\n\n\n\n\n\n\nrtip\n\n\nbrvm\n\n\nmarkets\n\n\n\n\n\n\n\n\n\n\n\nApr 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nLooking at Daily Log Returns with tidyquant, TidyDensity, and Shiny\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ntidydensity\n\n\ntidyquant\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nApr 5, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA sample Shiny App to view Forecasts on the AirPassengers Data\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ndata\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nApr 4, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA sample Shiny App to view CMS Healthcare Data\n\n\n\n\n\n\n\nrtip\n\n\nshiny\n\n\ndata\n\n\nhealthcare\n\n\ncms\n\n\n\n\n\n\n\n\n\n\n\nApr 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nA Bootstrapped Time Series Model with auto.arima() from {forecast}\n\n\n\n\n\n\n\nrtip\n\n\ntimeseries\n\n\nbootstrap\n\n\n\n\n\n\n\n\n\n\n\nMar 29, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow fast does a compressed file in Part 2\n\n\n\n\n\n\n\nrtip\n\n\nbenchmark\n\n\narrow\n\n\nduckdb\n\n\ndatatable\n\n\nreadr\n\n\n\n\n\n\n\n\n\n\n\nMar 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow fast does a compressed file in?\n\n\n\n\n\n\n\nrtip\n\n\nbenchmark\n\n\n\n\n\n\n\n\n\n\n\nMar 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHow fast do the files read in?\n\n\n\n\n\n\n\nrtip\n\n\nbenchmark\n\n\n\n\n\n\n\n\n\n\n\nMar 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSome Examples of Cumulative Mean with {TidyDensity}\n\n\n\n\n\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nMar 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGetting the CCI30 Index Current Makeup\n\n\n\n\n\n\n\ncrypto\n\n\ncci30\n\n\n\n\n\n\n\n\n\n\n\nMar 21, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUse of the apply family of functions\n\n\n\n\n\n\n\nthanks\n\n\n\n\n\n\n\n\n\n\n\nMar 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUse of the apply family of functions\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\napply\n\n\n\n\n\n\n\n\n\n\n\nMar 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMultiple Solutions to speedup tidy_bernoulli() with {data.table}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\ndatatable\n\n\n\n\n\n\n\n\n\n\n\nMar 9, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGetting NYS Home Heating Oil Prices with {rvest}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nrvest\n\n\n\n\n\n\n\n\n\n\n\nMar 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\ntidy_bernoulli() with {data.table}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ndatatable\n\n\n\n\n\n\n\n\n\n\n\nMar 7, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimple examples of imap() from {purrr}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nMar 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimple examples of pmap() from {purrr}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nMar 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nForecasting Timeseries in a list with R\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nMar 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nText Processing Made Easy with {healthyR}’s sql_left(), sql_mid(), and sql_right() Functions in R\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nsql\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nMar 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nOpen a File Folder in R\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nshell\n\n\n\n\n\n\n\n\n\n\n\nFeb 28, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nQuickly Generate Nested Time Series Models\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nautoarima\n\n\n\n\n\n\n\n\n\n\n\nFeb 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nData Preppers with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\npreprocessor\n\n\n\n\n\n\n\n\n\n\n\nFeb 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCalibrate and Plot a Time Series with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nFeb 22, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nConverting a {tidyAML} tibble to a {workflowsets}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\nworkflowsets\n\n\ntidymodels\n\n\n\n\n\n\n\n\n\n\n\nFeb 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nOfficially on CRAN {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nFeb 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMoving Average Plots with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nFeb 15, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAn example of using {box}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nbox\n\n\n\n\n\n\n\n\n\n\n\nFeb 14, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nOff to CRAN! {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\ntidymodels\n\n\n\n\n\n\n\n\n\n\n\nFeb 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGet the Current Hospital Data Set from CMS with {healthyR.data}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrdata\n\n\n\n\n\n\n\n\n\n\n\nFeb 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating and Predicting Fast Regression Parsnip Models with {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\n\n\n\n\n\n\n\n\n\nFeb 9, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreating an R Project Directory\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nFeb 8, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSubsetting Named Lists in R\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlist\n\n\n\n\n\n\n\n\n\n\n\nFeb 7, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCumulative Measurement Functions with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nFeb 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe Argument Matcher: A Function for Selecting the Right Arguments {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlist\n\n\ntidyaml\n\n\ntidymodels\n\n\n\n\n\n\n\n\n\n\n\nFeb 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDiverging Lollipop Chart: A Visual Tool for Comparing Data with {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyr\n\n\nplots\n\n\n\n\n\n\n\n\n\n\n\nFeb 2, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAttributes in R Functions: An Overview\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nmetadata\n\n\nattributes\n\n\n\n\n\n\n\n\n\n\n\nFeb 1, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMedian: A Simple Way to Detect Excess Events Over Time with {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nJan 31, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\n{healthyR.ts}: The New and Improved Library for Time Series Analysis\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nJan 30, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nService Line Grouping with {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\naugment\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nJan 27, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nTransforming Your Data: A Guide to Popular Methods and How to Implement Them with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntransforms\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nJan 26, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimplifying List Filtering in R with purrr’s keep()\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlists\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nJan 25, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMaking Non Stationary Data Stationary\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlists\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nJan 24, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nADF and Phillips-Perron Tests for Stationarity using lists\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlists\n\n\ntimeseries\n\n\nlapply\n\n\n\n\n\n\n\n\n\n\n\nJan 23, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAnother Post on Lists\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlists\n\n\n\n\n\n\n\n\n\n\n\nJan 20, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBoilerplate XGBoost with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nxgboost\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nJan 19, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGeometric Brownian Motion with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nJan 18, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAugmenting a Brownian Motion to a Time Series with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nJan 17, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAuto K-Means with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nkmeans\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nJan 16, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nThe building of {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nJan 13, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAn Update on {tidyAML}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidyaml\n\n\nautoml\n\n\n\n\n\n\n\n\n\n\n\nJan 12, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nReflecting on the Past Year: A LinkedIn Year in Review\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlinkedin\n\n\n\n\n\n\n\n\n\n\n\nJan 11, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nOptimal Break Points for Histograms with {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyr\n\n\nhistograms\n\n\n\n\n\n\n\n\n\n\n\nJan 10, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nNew Release of {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nJan 9, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBrownian Motion\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nweeklytip\n\n\n\n\n\n\n\n\n\n\n\nJan 6, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMore Randomwalks with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\nrandomwalk\n\n\n\n\n\n\n\n\n\n\n\nJan 5, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCalendar Heatmap with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nJan 3, 2023\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nEvent Analysis with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrts\n\n\ntimeseries\n\n\n\n\n\n\n\n\n\n\n\nDec 30, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGartner Magic Chart and its usefulness in healthcare analytics with {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nDec 29, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimulating Time Series Model Forecasts with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\nhealthyrts\n\n\ntimeseries\n\n\nsimulation\n\n\n\n\n\n\n\n\n\n\n\nDec 23, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nListing Functions and Parameters\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ndplyr\n\n\n\n\n\n\n\n\n\n\n\nDec 22, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDistribution Statistics with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nDec 21, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nRandom Walks with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nrandomwalk\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nDec 20, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nViewing Different Versions of the Same Statistical Distribution with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ndistributions\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nDec 19, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nModel Scedacity Plots with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nDec 16, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimple Moving Average Plots with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nDec 15, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDistribution Summaries with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nDec 14, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMixture Distributions with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\nmixturemodels\n\n\n\n\n\n\n\n\n\n\n\nDec 13, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreate QQ Plots for Time Series Models with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nDec 9, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreate a Faceted Historgram Plot with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhistograms\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nDec 8, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCreate Multiple {parsnip} Model Specs with {purrr}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nparsnip\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nDec 7, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nZ-Score Scaling Step Recipe with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nrecipes\n\n\n\n\n\n\n\n\n\n\n\nDec 6, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nNaming Items in a List with {purrr}, {dplyr}, or {healthyR}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\nhealthyr\n\n\n\n\n\n\n\n\n\n\n\nDec 5, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAuto KNN with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\nknn\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nDec 2, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nExtract Boilerplate Workflow Metrics with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nDec 1, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nGenerate Random Walk Data with {healthyR.ts}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntimeseries\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nNov 30, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWorking with Lists\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nlists\n\n\nlapply\n\n\n\n\n\n\n\n\n\n\n\nNov 29, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDefault Metric Sets with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nNov 28, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSummary Statistics with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\ntidydensity\n\n\ndatatable\n\n\n\n\n\n\n\n\n\n\n\nNov 23, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nData Preprocessing Scale/Normalize with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nrecipes\n\n\n\n\n\n\n\n\n\n\n\nNov 22, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBootstrap Modeling with Base R\n\n\n\n\n\n\n\ncode\n\n\nbootstrap\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nNov 21, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nUpdates to {healthyverse} packages\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\nhealthyverse\n\n\n\n\n\n\n\n\n\n\n\nNov 18, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBootstrap Modeling with {purrr} and {modler}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\nmodelr\n\n\n\n\n\n\n\n\n\n\n\nNov 17, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCumulative Harmonic Mean with {TidyDensity}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nNov 16, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nAuto Prep data for XGBoost with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nxgboost\n\n\n\n\n\n\n\n\n\n\n\nNov 15, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nFind Skewed Features with {healthyR.ai}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nskew\n\n\n\n\n\n\n\n\n\n\n\nNov 14, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nTime Series Lag Correlation Plots\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrts\n\n\nweeklytip\n\n\n\n\n\n\n\n\n\n\n\nNov 11, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nReading Multiple Files with {purrr}\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\npurrr\n\n\n\n\n\n\n\n\n\n\n\nNov 10, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nMapping K-Means with healthyR.ai\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nkmeans\n\n\n\n\n\n\n\n\n\n\n\nNov 9, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nHyperbolic Transform with healthyR.ai\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nNov 8, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nDiscrete Fourier Vec with healthyR.ai\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nNov 7, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nBootstrapping and Plots with TidyDensity\n\n\n\n\n\n\n\ncode\n\n\ntidydensity\n\n\nbootstrap\n\n\nweeklytip\n\n\n\n\n\n\n\n\n\n\n\nNov 4, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCumulative Skewness\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\n\n\n\n\n\n\n\n\n\nOct 31, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nPCA with healthyR.ai\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\nweeklytip\n\n\n\n\n\n\n\n\n\n\n\nOct 28, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nControl Charts in healthyR.ai\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nOct 26, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nCumulative Variance\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\ncumulative\n\n\nsapply\n\n\nlapply\n\n\n\n\n\n\n\n\n\n\n\nOct 24, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nTime Series Clustering with healthyR.ts\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\nhealthyrts\n\n\n\n\n\n\n\n\n\n\n\nOct 21, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nhealthyR.ai Primer\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\nhealthyrai\n\n\n\n\n\n\n\n\n\n\n\nOct 13, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nTidyDensity Primer\n\n\n\n\n\n\n\ncode\n\n\nweeklytip\n\n\ntidydensity\n\n\n\n\n\n\n\n\n\n\n\nOct 7, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nSimple lapply()\n\n\n\n\n\n\n\ncode\n\n\nrtip\n\n\nweeklytip\n\n\n\n\n\n\n\n\n\n\n\nOct 5, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\n \n\n\n\n\nWelcome To Steve On Data\n\n\n\n\n\n\n\nnews\n\n\n\n\n\n\n\n\n\n\n\nOct 5, 2022\n\n\nSteven P. Sanderson II, MPH\n\n\n\n\n\n\nNo matching items" }, { "objectID": "posts/healthyrai-20221013/index.html", @@ -3932,5 +3932,82 @@ "title": "How to Perform Multiple Linear Regression in R", "section": "Step 7: Encourage readers to try it themselves", "text": "Step 7: Encourage readers to try it themselves\nI’d encourage readers to take the code snippets, run them in their R environment, and explore. Maybe try different variables, tweak the model, or even use another dataset. Hands-on experience is the best teacher!\nRemember, understanding the data and interpreting the results is as important as running the code. It’s a fascinating journey into uncovering patterns and relationships within your data.\nFeel free to reach out if you have any questions or if there’s anything specific you’d like to explore further. Happy coding!" + }, + { + "objectID": "posts/2011-11-16/index.html", + "href": "posts/2011-11-16/index.html", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "", + "text": "My R package {healthyR.ts} has been updated to version 0.3.0; you can install it from either CRAN, r-universe or GitHub. Let’s go over some of the changes and improvements." + }, + { + "objectID": "posts/2011-11-16/index.html#util_log_ts---logging-time-series-data", + "href": "posts/2011-11-16/index.html#util_log_ts---logging-time-series-data", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "1. util_log_ts() - Logging Time Series Data", + "text": "1. util_log_ts() - Logging Time Series Data\nOne of the standout additions is the introduction of util_log_ts(). This function seems like a game-changer, providing a streamlined way to log time series data. This is incredibly useful, especially when dealing with extensive datasets, making the whole process more efficient and user-friendly. This is a helper function for auto_stationarize()." + }, + { + "objectID": "posts/2011-11-16/index.html#util_singlediff_ts---single-differences-for-time-series", + "href": "posts/2011-11-16/index.html#util_singlediff_ts---single-differences-for-time-series", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "2. util_singlediff_ts() - Single Differences for Time Series", + "text": "2. util_singlediff_ts() - Single Differences for Time Series\nThe addition of util_singlediff_ts() expands the toolkit, offering a function dedicated to handling single differences in time series data. This is valuable for various applications, such as identifying trends or preparing data for further analysis. This is a helper function for auto_stationarize()." + }, + { + "objectID": "posts/2011-11-16/index.html#util_doublediff_ts---double-differences-for-time-series", + "href": "posts/2011-11-16/index.html#util_doublediff_ts---double-differences-for-time-series", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "3. util_doublediff_ts() - Double Differences for Time Series", + "text": "3. util_doublediff_ts() - Double Differences for Time Series\nBuilding on the concept of differencing, util_doublediff_ts() seems to provide a higher level of sophistication, allowing users to perform double differences on time series data. This could be pivotal in cases where a more refined analysis is required. This is a helper function for auto_stationarize()." + }, + { + "objectID": "posts/2011-11-16/index.html#util_difflog_ts---combining-differences-and-log-transformation", + "href": "posts/2011-11-16/index.html#util_difflog_ts---combining-differences-and-log-transformation", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "4. util_difflog_ts() - Combining Differences and Log Transformation", + "text": "4. util_difflog_ts() - Combining Differences and Log Transformation\nThe fusion of differencing and log transformation in util_difflog_ts() is a remarkable addition. This could be particularly beneficial in scenarios where both operations are needed to unlock deeper insights from the time series data. This is a helper function for auto_stationarize()." + }, + { + "objectID": "posts/2011-11-16/index.html#util_doubledifflog_ts---double-differences-with-log-transformation", + "href": "posts/2011-11-16/index.html#util_doubledifflog_ts---double-differences-with-log-transformation", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "5. util_doubledifflog_ts() - Double Differences with Log Transformation", + "text": "5. util_doubledifflog_ts() - Double Differences with Log Transformation\nThe introduction of util_doubledifflog_ts() appears to take things a step further by combining double differences and log transformation. This function seems poised to provide a comprehensive solution for users dealing with complex time series data. This is a helper function for auto_stationarize()." + }, + { + "objectID": "posts/2011-11-16/index.html#attributes-enhancement-in-ts_growth_rate_vec", + "href": "posts/2011-11-16/index.html#attributes-enhancement-in-ts_growth_rate_vec", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "1. Attributes Enhancement in ts_growth_rate_vec()", + "text": "1. Attributes Enhancement in ts_growth_rate_vec()\nThe attention to detail is evident with the addition of attributes to the output of ts_growth_rate_vec(). This enhancement not only improves the clarity of results but also contributes to a more informative and user-friendly experience." + }, + { + "objectID": "posts/2011-11-16/index.html#refinement-of-auto_stationarize-in-response-to-user-feedback", + "href": "posts/2011-11-16/index.html#refinement-of-auto_stationarize-in-response-to-user-feedback", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "2. Refinement of auto_stationarize() in Response to User Feedback", + "text": "2. Refinement of auto_stationarize() in Response to User Feedback\nUpdates to auto_stationarize() based on user feedback (Fix #481 #483) demonstrate a commitment to refining existing features. This responsiveness to the community’s needs is commendable and ensures that the package evolves in sync with user expectations. It has taken all of the util_ transforms mentioned above in order to improve it’s functionality." + }, + { + "objectID": "posts/2011-11-16/index.html#integration-with-auto_arima-engine-in-ts_auto_arima", + "href": "posts/2011-11-16/index.html#integration-with-auto_arima-engine-in-ts_auto_arima", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "3. Integration with auto_arima Engine in ts_auto_arima()", + "text": "3. Integration with auto_arima Engine in ts_auto_arima()\nThe integration of ts_auto_arima() with the parsnip engine of auto_arima is a notable improvement. This update, triggered when .tune is set to FALSE, aligns the package with cutting-edge tools, potentially enhancing the efficiency and accuracy of time series modeling.\nIn conclusion, the release of healthyR.ts version 0.3.0 is an exciting leap forward. The new features introduce powerful capabilities, while the minor fixes and improvements showcase a commitment to providing a robust and user-friendly package. Users can look forward to a more versatile and refined experience in time series analysis. Great job on this release, and I’m sure the community is eager to explore these enhancements!" + }, + { + "objectID": "posts/2011-11-16/index.html#auto_stationarize", + "href": "posts/2011-11-16/index.html#auto_stationarize", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "auto_stationarize()", + "text": "auto_stationarize()\n\nlibrary(healthyR.ts)\n\nauto_stationarize(AirPassengers)\n\nThe time series is already stationary via ts_adf_test().\n\n\n Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec\n1949 112 118 132 129 121 135 148 148 136 119 104 118\n1950 115 126 141 135 125 149 170 170 158 133 114 140\n1951 145 150 178 163 172 178 199 199 184 162 146 166\n1952 171 180 193 181 183 218 230 242 209 191 172 194\n1953 196 196 236 235 229 243 264 272 237 211 180 201\n1954 204 188 235 227 234 264 302 293 259 229 203 229\n1955 242 233 267 269 270 315 364 347 312 274 237 278\n1956 284 277 317 313 318 374 413 405 355 306 271 306\n1957 315 301 356 348 355 422 465 467 404 347 305 336\n1958 340 318 362 348 363 435 491 505 404 359 310 337\n1959 360 342 406 396 420 472 548 559 463 407 362 405\n1960 417 391 419 461 472 535 622 606 508 461 390 432\n\nauto_stationarize(BJsales)\n\nThe time series is not stationary. Attempting to make it stationary...\n\n\n$stationary_ts\nTime Series:\nStart = 3 \nEnd = 150 \nFrequency = 1 \n [1] 0.5 -0.4 0.6 1.1 -2.8 3.0 -1.1 0.6 -0.5 -0.5 0.1 2.0 -0.6 0.8 1.2\n [16] -3.4 -0.7 -0.3 1.7 3.0 -3.2 0.9 2.2 -2.5 -0.4 2.6 -4.3 2.0 -3.1 2.7\n [31] -2.1 0.1 2.1 -0.2 -2.2 0.6 1.0 -2.6 3.0 0.3 0.2 -0.8 1.0 0.0 3.2\n [46] -2.2 -4.7 1.2 0.8 -0.6 -0.4 0.6 1.0 -1.6 -0.1 3.4 -0.9 -1.7 -0.5 0.8\n [61] 2.4 -1.9 0.6 -2.2 2.6 -0.1 -2.7 1.7 -0.3 1.9 -2.7 1.1 -0.6 0.9 0.0\n [76] 1.8 -0.5 -0.4 -1.2 2.6 -1.8 1.7 -0.9 0.6 -0.4 3.0 -2.8 3.1 -2.3 -1.1\n [91] 2.1 -0.3 -1.7 -0.8 -0.4 1.1 -1.5 0.3 1.4 -2.0 1.3 -0.3 0.4 -3.5 1.1\n[106] 2.6 0.4 -1.3 2.0 -1.6 0.6 -0.1 -1.4 1.6 1.6 -3.4 1.7 -2.2 2.1 -2.0\n[121] -0.2 0.2 0.7 -1.4 1.8 -0.1 -0.7 0.4 0.4 1.0 -2.4 1.0 -0.4 0.8 -1.0\n[136] 1.4 -1.2 1.1 -0.9 0.5 1.9 -0.6 0.3 -1.4 -0.9 -0.5 1.4 0.1\n\n$ndiffs\n[1] 1\n\n$adf_stats\n$adf_stats$test_stat\n[1] -6.562008\n\n$adf_stats$p_value\n[1] 0.01\n\n\n$trans_type\n[1] \"double_diff\"\n\n$ret\n[1] TRUE\n\nplot.ts(auto_stationarize(BJsales)$stationary_ts)\n\nThe time series is not stationary. Attempting to make it stationary...\n\n\n\n\nauto_stationarize(BJsales.lead)\n\nThe time series is not stationary. Attempting to make it stationary...\n\n\n$stationary_ts\nTime Series:\nStart = 2 \nEnd = 150 \nFrequency = 1 \n [1] 0.06 0.25 -0.57 0.58 -0.20 0.23 -0.04 -0.19 0.03 0.42 0.04 0.24\n [13] 0.34 -0.46 -0.18 -0.08 0.29 0.56 -0.37 0.20 0.54 -0.31 0.03 0.52\n [25] -0.70 0.35 -0.63 0.44 -0.38 -0.01 0.22 0.10 -0.50 0.01 0.30 -0.76\n [37] 0.52 0.15 0.06 -0.10 0.21 -0.01 0.70 -0.22 -0.76 0.06 0.02 -0.17\n [49] -0.08 0.01 0.11 -0.39 0.01 0.50 -0.02 -0.37 -0.13 0.05 0.54 -0.46\n [61] 0.25 -0.52 0.44 0.02 -0.47 0.11 0.06 0.25 -0.35 0.00 -0.06 0.21\n [73] -0.09 0.36 0.09 -0.04 -0.20 0.44 -0.23 0.40 -0.01 0.17 0.08 0.58\n [85] -0.27 0.79 -0.21 0.02 0.30 0.28 -0.27 -0.01 0.03 0.16 -0.28 0.15\n [97] 0.26 -0.36 0.32 -0.11 0.22 -0.65 0.00 0.47 0.16 -0.19 0.48 -0.26\n[109] 0.21 0.00 -0.20 0.35 0.38 -0.48 0.20 -0.32 0.43 -0.50 0.12 -0.17\n[121] 0.15 -0.36 0.35 -0.03 -0.18 0.16 0.07 0.21 -0.50 0.23 -0.13 0.14\n[133] -0.15 0.19 -0.24 0.26 -0.22 0.17 0.37 -0.06 0.29 -0.34 -0.12 -0.16\n[145] 0.25 0.08 -0.07 0.26 -0.37\n\n$ndiffs\n[1] 1\n\n$adf_stats\n$adf_stats$test_stat\n[1] -4.838625\n\n$adf_stats$p_value\n[1] 0.01\n\n\n$trans_type\n[1] \"diff\"\n\n$ret\n[1] TRUE\n\nplot.ts(auto_stationarize(BJsales.lead)$stationary_ts)\n\nThe time series is not stationary. Attempting to make it stationary..." + }, + { + "objectID": "posts/2011-11-16/index.html#ts_auto_arima", + "href": "posts/2011-11-16/index.html#ts_auto_arima", + "title": "{healthyR.ts} New Features: Unlocking More Power", + "section": "ts_auto_arima()", + "text": "ts_auto_arima()\nThis use to only use the Arima engine if the .tune parameter was set to FALSE, thus it would many times give a simple straight line forecast. This was changed to make the engine auto_arima if .tune is set to FALSE.\n\nlibrary(timetk)\nlibrary(dplyr)\nlibrary(modeltime)\n\ndata <- AirPassengers |>\n ts_to_tbl() |>\n select(-index)\n\nsplits <- time_series_split(\n data\n , date_col\n , assess = 12\n , skip = 3\n , cumulative = TRUE\n)\n\nts_aa <- ts_auto_arima(\n .data = data,\n .num_cores = 2,\n .date_col = date_col,\n .value_col = value,\n .rsamp_obj = splits,\n .formula = value ~ .,\n .grid_size = 5,\n .cv_slice_limit = 2,\n .tune = FALSE\n)\n\nts_aa$recipe_info\n\n$recipe_call\nrecipe(.data = data, .date_col = date_col, .value_col = value, \n .formula = value ~ ., .rsamp_obj = splits, .tune = FALSE, \n .grid_size = 5, .num_cores = 2, .cv_slice_limit = 2)\n\n$recipe_syntax\n[1] \"ts_arima_recipe <-\" \n[2] \"\\n recipe(.data = data, .date_col = date_col, .value_col = value, .formula = value ~ \\n ., .rsamp_obj = splits, .tune = FALSE, .grid_size = 5, .num_cores = 2, \\n .cv_slice_limit = 2)\"\n\n$rec_obj\n\nts_aa$model_info\n\n$model_spec\nARIMA Regression Model Specification (regression)\n\nComputational engine: auto_arima \n\n\n$wflw\n══ Workflow ════════════════════════════════════════════════════════════════════\nPreprocessor: Recipe\nModel: arima_reg()\n\n── Preprocessor ────────────────────────────────────────────────────────────────\n0 Recipe Steps\n\n── Model ───────────────────────────────────────────────────────────────────────\nARIMA Regression Model Specification (regression)\n\nComputational engine: auto_arima \n\n\n$fitted_wflw\n══ Workflow [trained] ══════════════════════════════════════════════════════════\nPreprocessor: Recipe\nModel: arima_reg()\n\n── Preprocessor ────────────────────────────────────────────────────────────────\n0 Recipe Steps\n\n── Model ───────────────────────────────────────────────────────────────────────\nSeries: outcome \nARIMA(1,1,0)(0,1,0)[12] \n\nCoefficients:\n ar1\n -0.2431\ns.e. 0.0894\n\nsigma^2 = 109.8: log likelihood = -447.95\nAIC=899.9 AICc=900.01 BIC=905.46\n\n$was_tuned\n[1] \"not_tuned\"\n\nts_aa$model_calibration\n\n$plot\n\n$calibration_tbl\n# Modeltime Table\n# A tibble: 1 × 5\n .model_id .model .model_desc .type .calibration_data\n <int> <list> <chr> <chr> <list> \n1 1 <workflow> ARIMA(1,1,0)(0,1,0)[12] Test <tibble [12 × 4]>\n\n$model_accuracy\n# A tibble: 1 × 9\n .model_id .model_desc .type mae mape mase smape rmse rsq\n <int> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>\n1 1 ARIMA(1,1,0)(0,1,0)[12] Test 18.5 4.18 0.384 4.03 23.9 0.955\n\nts_aa$model_calibration$plot\n\n\n\n\n\nFinally enhancement to add attributes to ts_growth_rate_vec()\n\nts_growth_rate_vec(AirPassengers)\n\n [1] NA 5.3571429 11.8644068 -2.2727273 -6.2015504 11.5702479\n [7] 9.6296296 0.0000000 -8.1081081 -12.5000000 -12.6050420 13.4615385\n [13] -2.5423729 9.5652174 11.9047619 -4.2553191 -7.4074074 19.2000000\n [19] 14.0939597 0.0000000 -7.0588235 -15.8227848 -14.2857143 22.8070175\n [25] 3.5714286 3.4482759 18.6666667 -8.4269663 5.5214724 3.4883721\n [31] 11.7977528 0.0000000 -7.5376884 -11.9565217 -9.8765432 13.6986301\n [37] 3.0120482 5.2631579 7.2222222 -6.2176166 1.1049724 19.1256831\n [43] 5.5045872 5.2173913 -13.6363636 -8.6124402 -9.9476440 12.7906977\n [49] 1.0309278 0.0000000 20.4081633 -0.4237288 -2.5531915 6.1135371\n [55] 8.6419753 3.0303030 -12.8676471 -10.9704641 -14.6919431 11.6666667\n [61] 1.4925373 -7.8431373 25.0000000 -3.4042553 3.0837004 12.8205128\n [67] 14.3939394 -2.9801325 -11.6040956 -11.5830116 -11.3537118 12.8078818\n [73] 5.6768559 -3.7190083 14.5922747 0.7490637 0.3717472 16.6666667\n [79] 15.5555556 -4.6703297 -10.0864553 -12.1794872 -13.5036496 17.2995781\n [85] 2.1582734 -2.4647887 14.4404332 -1.2618297 1.5974441 17.6100629\n [91] 10.4278075 -1.9370460 -12.3456790 -13.8028169 -11.4379085 12.9151292\n [97] 2.9411765 -4.4444444 18.2724252 -2.2471910 2.0114943 18.8732394\n[103] 10.1895735 0.4301075 -13.4903640 -14.1089109 -12.1037464 10.1639344\n[109] 1.1904762 -6.4705882 13.8364780 -3.8674033 4.3103448 19.8347107\n[115] 12.8735632 2.8513238 -20.0000000 -11.1386139 -13.6490251 8.7096774\n[121] 6.8249258 -5.0000000 18.7134503 -2.4630542 6.0606061 12.3809524\n[127] 16.1016949 2.0072993 -17.1735242 -12.0950324 -11.0565111 11.8784530\n[133] 2.9629630 -6.2350120 7.1611253 10.0238663 2.3861171 13.3474576\n[139] 16.2616822 -2.5723473 -16.1716172 -9.2519685 -15.4013015 10.7692308\nattr(,\"vector_attributes\")\nattr(,\"vector_attributes\")$tsp\n[1] 1949.000 1960.917 12.000\n\nattr(,\"vector_attributes\")$class\n[1] \"ts\"\n\nattr(,\"name\")\n[1] \"AirPassengers\"" } ] \ No newline at end of file diff --git a/docs/site_libs/htmlwidgets-1.6.2/htmlwidgets.js b/docs/site_libs/htmlwidgets-1.6.2/htmlwidgets.js new file mode 100644 index 00000000..1067d029 --- /dev/null +++ b/docs/site_libs/htmlwidgets-1.6.2/htmlwidgets.js @@ -0,0 +1,901 @@ +(function() { + // If window.HTMLWidgets is already defined, then use it; otherwise create a + // new object. This allows preceding code to set options that affect the + // initialization process (though none currently exist). + window.HTMLWidgets = window.HTMLWidgets || {}; + + // See if we're running in a viewer pane. If not, we're in a web browser. + var viewerMode = window.HTMLWidgets.viewerMode = + /\bviewer_pane=1\b/.test(window.location); + + // See if we're running in Shiny mode. If not, it's a static document. + // Note that static widgets can appear in both Shiny and static modes, but + // obviously, Shiny widgets can only appear in Shiny apps/documents. + var shinyMode = window.HTMLWidgets.shinyMode = + typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings; + + // We can't count on jQuery being available, so we implement our own + // version if necessary. + function querySelectorAll(scope, selector) { + if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) { + return scope.find(selector); + } + if (scope.querySelectorAll) { + return scope.querySelectorAll(selector); + } + } + + function asArray(value) { + if (value === null) + return []; + if ($.isArray(value)) + return value; + return [value]; + } + + // Implement jQuery's extend + function extend(target /*, ... */) { + if (arguments.length == 1) { + return target; + } + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var prop in source) { + if (source.hasOwnProperty(prop)) { + target[prop] = source[prop]; + } + } + } + return target; + } + + // IE8 doesn't support Array.forEach. + function forEach(values, callback, thisArg) { + if (values.forEach) { + values.forEach(callback, thisArg); + } else { + for (var i = 0; i < values.length; i++) { + callback.call(thisArg, values[i], i, values); + } + } + } + + // Replaces the specified method with the return value of funcSource. + // + // Note that funcSource should not BE the new method, it should be a function + // that RETURNS the new method. funcSource receives a single argument that is + // the overridden method, it can be called from the new method. The overridden + // method can be called like a regular function, it has the target permanently + // bound to it so "this" will work correctly. + function overrideMethod(target, methodName, funcSource) { + var superFunc = target[methodName] || function() {}; + var superFuncBound = function() { + return superFunc.apply(target, arguments); + }; + target[methodName] = funcSource(superFuncBound); + } + + // Add a method to delegator that, when invoked, calls + // delegatee.methodName. If there is no such method on + // the delegatee, but there was one on delegator before + // delegateMethod was called, then the original version + // is invoked instead. + // For example: + // + // var a = { + // method1: function() { console.log('a1'); } + // method2: function() { console.log('a2'); } + // }; + // var b = { + // method1: function() { console.log('b1'); } + // }; + // delegateMethod(a, b, "method1"); + // delegateMethod(a, b, "method2"); + // a.method1(); + // a.method2(); + // + // The output would be "b1", "a2". + function delegateMethod(delegator, delegatee, methodName) { + var inherited = delegator[methodName]; + delegator[methodName] = function() { + var target = delegatee; + var method = delegatee[methodName]; + + // The method doesn't exist on the delegatee. Instead, + // call the method on the delegator, if it exists. + if (!method) { + target = delegator; + method = inherited; + } + + if (method) { + return method.apply(target, arguments); + } + }; + } + + // Implement a vague facsimilie of jQuery's data method + function elementData(el, name, value) { + if (arguments.length == 2) { + return el["htmlwidget_data_" + name]; + } else if (arguments.length == 3) { + el["htmlwidget_data_" + name] = value; + return el; + } else { + throw new Error("Wrong number of arguments for elementData: " + + arguments.length); + } + } + + // http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex + function escapeRegExp(str) { + return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&"); + } + + function hasClass(el, className) { + var re = new RegExp("\\b" + escapeRegExp(className) + "\\b"); + return re.test(el.className); + } + + // elements - array (or array-like object) of HTML elements + // className - class name to test for + // include - if true, only return elements with given className; + // if false, only return elements *without* given className + function filterByClass(elements, className, include) { + var results = []; + for (var i = 0; i < elements.length; i++) { + if (hasClass(elements[i], className) == include) + results.push(elements[i]); + } + return results; + } + + function on(obj, eventName, func) { + if (obj.addEventListener) { + obj.addEventListener(eventName, func, false); + } else if (obj.attachEvent) { + obj.attachEvent(eventName, func); + } + } + + function off(obj, eventName, func) { + if (obj.removeEventListener) + obj.removeEventListener(eventName, func, false); + else if (obj.detachEvent) { + obj.detachEvent(eventName, func); + } + } + + // Translate array of values to top/right/bottom/left, as usual with + // the "padding" CSS property + // https://developer.mozilla.org/en-US/docs/Web/CSS/padding + function unpackPadding(value) { + if (typeof(value) === "number") + value = [value]; + if (value.length === 1) { + return {top: value[0], right: value[0], bottom: value[0], left: value[0]}; + } + if (value.length === 2) { + return {top: value[0], right: value[1], bottom: value[0], left: value[1]}; + } + if (value.length === 3) { + return {top: value[0], right: value[1], bottom: value[2], left: value[1]}; + } + if (value.length === 4) { + return {top: value[0], right: value[1], bottom: value[2], left: value[3]}; + } + } + + // Convert an unpacked padding object to a CSS value + function paddingToCss(paddingObj) { + return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px"; + } + + // Makes a number suitable for CSS + function px(x) { + if (typeof(x) === "number") + return x + "px"; + else + return x; + } + + // Retrieves runtime widget sizing information for an element. + // The return value is either null, or an object with fill, padding, + // defaultWidth, defaultHeight fields. + function sizingPolicy(el) { + var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']"); + if (!sizingEl) + return null; + var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}"); + if (viewerMode) { + return sp.viewer; + } else { + return sp.browser; + } + } + + // @param tasks Array of strings (or falsy value, in which case no-op). + // Each element must be a valid JavaScript expression that yields a + // function. Or, can be an array of objects with "code" and "data" + // properties; in this case, the "code" property should be a string + // of JS that's an expr that yields a function, and "data" should be + // an object that will be added as an additional argument when that + // function is called. + // @param target The object that will be "this" for each function + // execution. + // @param args Array of arguments to be passed to the functions. (The + // same arguments will be passed to all functions.) + function evalAndRun(tasks, target, args) { + if (tasks) { + forEach(tasks, function(task) { + var theseArgs = args; + if (typeof(task) === "object") { + theseArgs = theseArgs.concat([task.data]); + task = task.code; + } + var taskFunc = tryEval(task); + if (typeof(taskFunc) !== "function") { + throw new Error("Task must be a function! Source:\n" + task); + } + taskFunc.apply(target, theseArgs); + }); + } + } + + // Attempt eval() both with and without enclosing in parentheses. + // Note that enclosing coerces a function declaration into + // an expression that eval() can parse + // (otherwise, a SyntaxError is thrown) + function tryEval(code) { + var result = null; + try { + result = eval("(" + code + ")"); + } catch(error) { + if (!(error instanceof SyntaxError)) { + throw error; + } + try { + result = eval(code); + } catch(e) { + if (e instanceof SyntaxError) { + throw error; + } else { + throw e; + } + } + } + return result; + } + + function initSizing(el) { + var sizing = sizingPolicy(el); + if (!sizing) + return; + + var cel = document.getElementById("htmlwidget_container"); + if (!cel) + return; + + if (typeof(sizing.padding) !== "undefined") { + document.body.style.margin = "0"; + document.body.style.padding = paddingToCss(unpackPadding(sizing.padding)); + } + + if (sizing.fill) { + document.body.style.overflow = "hidden"; + document.body.style.width = "100%"; + document.body.style.height = "100%"; + document.documentElement.style.width = "100%"; + document.documentElement.style.height = "100%"; + cel.style.position = "absolute"; + var pad = unpackPadding(sizing.padding); + cel.style.top = pad.top + "px"; + cel.style.right = pad.right + "px"; + cel.style.bottom = pad.bottom + "px"; + cel.style.left = pad.left + "px"; + el.style.width = "100%"; + el.style.height = "100%"; + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + + } else { + el.style.width = px(sizing.width); + el.style.height = px(sizing.height); + + return { + getWidth: function() { return cel.getBoundingClientRect().width; }, + getHeight: function() { return cel.getBoundingClientRect().height; } + }; + } + } + + // Default implementations for methods + var defaults = { + find: function(scope) { + return querySelectorAll(scope, "." + this.name); + }, + renderError: function(el, err) { + var $el = $(el); + + this.clearError(el); + + // Add all these error classes, as Shiny does + var errClass = "shiny-output-error"; + if (err.type !== null) { + // use the classes of the error condition as CSS class names + errClass = errClass + " " + $.map(asArray(err.type), function(type) { + return errClass + "-" + type; + }).join(" "); + } + errClass = errClass + " htmlwidgets-error"; + + // Is el inline or block? If inline or inline-block, just display:none it + // and add an inline error. + var display = $el.css("display"); + $el.data("restore-display-mode", display); + + if (display === "inline" || display === "inline-block") { + $el.hide(); + if (err.message !== "") { + var errorSpan = $("").addClass(errClass); + errorSpan.text(err.message); + $el.after(errorSpan); + } + } else if (display === "block") { + // If block, add an error just after the el, set visibility:none on the + // el, and position the error to be on top of the el. + // Mark it with a unique ID and CSS class so we can remove it later. + $el.css("visibility", "hidden"); + if (err.message !== "") { + var errorDiv = $("
").addClass(errClass).css("position", "absolute") + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + // setting width can push out the page size, forcing otherwise + // unnecessary scrollbars to appear and making it impossible for + // the element to shrink; so use max-width instead + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + errorDiv.text(err.message); + $el.after(errorDiv); + + // Really dumb way to keep the size/position of the error in sync with + // the parent element as the window is resized or whatever. + var intId = setInterval(function() { + if (!errorDiv[0].parentElement) { + clearInterval(intId); + return; + } + errorDiv + .css("top", el.offsetTop) + .css("left", el.offsetLeft) + .css("maxWidth", el.offsetWidth) + .css("height", el.offsetHeight); + }, 500); + } + } + }, + clearError: function(el) { + var $el = $(el); + var display = $el.data("restore-display-mode"); + $el.data("restore-display-mode", null); + + if (display === "inline" || display === "inline-block") { + if (display) + $el.css("display", display); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } else if (display === "block"){ + $el.css("visibility", "inherit"); + $(el.nextSibling).filter(".htmlwidgets-error").remove(); + } + }, + sizing: {} + }; + + // Called by widget bindings to register a new type of widget. The definition + // object can contain the following properties: + // - name (required) - A string indicating the binding name, which will be + // used by default as the CSS classname to look for. + // - initialize (optional) - A function(el) that will be called once per + // widget element; if a value is returned, it will be passed as the third + // value to renderValue. + // - renderValue (required) - A function(el, data, initValue) that will be + // called with data. Static contexts will cause this to be called once per + // element; Shiny apps will cause this to be called multiple times per + // element, as the data changes. + window.HTMLWidgets.widget = function(definition) { + if (!definition.name) { + throw new Error("Widget must have a name"); + } + if (!definition.type) { + throw new Error("Widget must have a type"); + } + // Currently we only support output widgets + if (definition.type !== "output") { + throw new Error("Unrecognized widget type '" + definition.type + "'"); + } + // TODO: Verify that .name is a valid CSS classname + + // Support new-style instance-bound definitions. Old-style class-bound + // definitions have one widget "object" per widget per type/class of + // widget; the renderValue and resize methods on such widget objects + // take el and instance arguments, because the widget object can't + // store them. New-style instance-bound definitions have one widget + // object per widget instance; the definition that's passed in doesn't + // provide renderValue or resize methods at all, just the single method + // factory(el, width, height) + // which returns an object that has renderValue(x) and resize(w, h). + // This enables a far more natural programming style for the widget + // author, who can store per-instance state using either OO-style + // instance fields or functional-style closure variables (I guess this + // is in contrast to what can only be called C-style pseudo-OO which is + // what we required before). + if (definition.factory) { + definition = createLegacyDefinitionAdapter(definition); + } + + if (!definition.renderValue) { + throw new Error("Widget must have a renderValue function"); + } + + // For static rendering (non-Shiny), use a simple widget registration + // scheme. We also use this scheme for Shiny apps/documents that also + // contain static widgets. + window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || []; + // Merge defaults into the definition; don't mutate the original definition. + var staticBinding = extend({}, defaults, definition); + overrideMethod(staticBinding, "find", function(superfunc) { + return function(scope) { + var results = superfunc(scope); + // Filter out Shiny outputs, we only want the static kind + return filterByClass(results, "html-widget-output", false); + }; + }); + window.HTMLWidgets.widgets.push(staticBinding); + + if (shinyMode) { + // Shiny is running. Register the definition with an output binding. + // The definition itself will not be the output binding, instead + // we will make an output binding object that delegates to the + // definition. This is because we foolishly used the same method + // name (renderValue) for htmlwidgets definition and Shiny bindings + // but they actually have quite different semantics (the Shiny + // bindings receive data that includes lots of metadata that it + // strips off before calling htmlwidgets renderValue). We can't + // just ignore the difference because in some widgets it's helpful + // to call this.renderValue() from inside of resize(), and if + // we're not delegating, then that call will go to the Shiny + // version instead of the htmlwidgets version. + + // Merge defaults with definition, without mutating either. + var bindingDef = extend({}, defaults, definition); + + // This object will be our actual Shiny binding. + var shinyBinding = new Shiny.OutputBinding(); + + // With a few exceptions, we'll want to simply use the bindingDef's + // version of methods if they are available, otherwise fall back to + // Shiny's defaults. NOTE: If Shiny's output bindings gain additional + // methods in the future, and we want them to be overrideable by + // HTMLWidget binding definitions, then we'll need to add them to this + // list. + delegateMethod(shinyBinding, bindingDef, "getId"); + delegateMethod(shinyBinding, bindingDef, "onValueChange"); + delegateMethod(shinyBinding, bindingDef, "onValueError"); + delegateMethod(shinyBinding, bindingDef, "renderError"); + delegateMethod(shinyBinding, bindingDef, "clearError"); + delegateMethod(shinyBinding, bindingDef, "showProgress"); + + // The find, renderValue, and resize are handled differently, because we + // want to actually decorate the behavior of the bindingDef methods. + + shinyBinding.find = function(scope) { + var results = bindingDef.find(scope); + + // Only return elements that are Shiny outputs, not static ones + var dynamicResults = results.filter(".html-widget-output"); + + // It's possible that whatever caused Shiny to think there might be + // new dynamic outputs, also caused there to be new static outputs. + // Since there might be lots of different htmlwidgets bindings, we + // schedule execution for later--no need to staticRender multiple + // times. + if (results.length !== dynamicResults.length) + scheduleStaticRender(); + + return dynamicResults; + }; + + // Wrap renderValue to handle initialization, which unfortunately isn't + // supported natively by Shiny at the time of this writing. + + shinyBinding.renderValue = function(el, data) { + Shiny.renderDependencies(data.deps); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var i = 0; data.evals && i < data.evals.length; i++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]); + } + if (!bindingDef.renderOnNullValue) { + if (data.x === null) { + el.style.visibility = "hidden"; + return; + } else { + el.style.visibility = "inherit"; + } + } + if (!elementData(el, "initialized")) { + initSizing(el); + + elementData(el, "initialized", true); + if (bindingDef.initialize) { + var rect = el.getBoundingClientRect(); + var result = bindingDef.initialize(el, rect.width, rect.height); + elementData(el, "init_result", result); + } + } + bindingDef.renderValue(el, data.x, elementData(el, "init_result")); + evalAndRun(data.jsHooks.render, elementData(el, "init_result"), [el, data.x]); + }; + + // Only override resize if bindingDef implements it + if (bindingDef.resize) { + shinyBinding.resize = function(el, width, height) { + // Shiny can call resize before initialize/renderValue have been + // called, which doesn't make sense for widgets. + if (elementData(el, "initialized")) { + bindingDef.resize(el, width, height, elementData(el, "init_result")); + } + }; + } + + Shiny.outputBindings.register(shinyBinding, bindingDef.name); + } + }; + + var scheduleStaticRenderTimerId = null; + function scheduleStaticRender() { + if (!scheduleStaticRenderTimerId) { + scheduleStaticRenderTimerId = setTimeout(function() { + scheduleStaticRenderTimerId = null; + window.HTMLWidgets.staticRender(); + }, 1); + } + } + + // Render static widgets after the document finishes loading + // Statically render all elements that are of this widget's class + window.HTMLWidgets.staticRender = function() { + var bindings = window.HTMLWidgets.widgets || []; + forEach(bindings, function(binding) { + var matches = binding.find(document.documentElement); + forEach(matches, function(el) { + var sizeObj = initSizing(el, binding); + + var getSize = function(el) { + if (sizeObj) { + return {w: sizeObj.getWidth(), h: sizeObj.getHeight()} + } else { + var rect = el.getBoundingClientRect(); + return {w: rect.width, h: rect.height} + } + }; + + if (hasClass(el, "html-widget-static-bound")) + return; + el.className = el.className + " html-widget-static-bound"; + + var initResult; + if (binding.initialize) { + var size = getSize(el); + initResult = binding.initialize(el, size.w, size.h); + elementData(el, "init_result", initResult); + } + + if (binding.resize) { + var lastSize = getSize(el); + var resizeHandler = function(e) { + var size = getSize(el); + if (size.w === 0 && size.h === 0) + return; + if (size.w === lastSize.w && size.h === lastSize.h) + return; + lastSize = size; + binding.resize(el, size.w, size.h, initResult); + }; + + on(window, "resize", resizeHandler); + + // This is needed for cases where we're running in a Shiny + // app, but the widget itself is not a Shiny output, but + // rather a simple static widget. One example of this is + // an rmarkdown document that has runtime:shiny and widget + // that isn't in a render function. Shiny only knows to + // call resize handlers for Shiny outputs, not for static + // widgets, so we do it ourselves. + if (window.jQuery) { + window.jQuery(document).on( + "shown.htmlwidgets shown.bs.tab.htmlwidgets shown.bs.collapse.htmlwidgets", + resizeHandler + ); + window.jQuery(document).on( + "hidden.htmlwidgets hidden.bs.tab.htmlwidgets hidden.bs.collapse.htmlwidgets", + resizeHandler + ); + } + + // This is needed for the specific case of ioslides, which + // flips slides between display:none and display:block. + // Ideally we would not have to have ioslide-specific code + // here, but rather have ioslides raise a generic event, + // but the rmarkdown package just went to CRAN so the + // window to getting that fixed may be long. + if (window.addEventListener) { + // It's OK to limit this to window.addEventListener + // browsers because ioslides itself only supports + // such browsers. + on(document, "slideenter", resizeHandler); + on(document, "slideleave", resizeHandler); + } + } + + var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']"); + if (scriptData) { + var data = JSON.parse(scriptData.textContent || scriptData.text); + // Resolve strings marked as javascript literals to objects + if (!(data.evals instanceof Array)) data.evals = [data.evals]; + for (var k = 0; data.evals && k < data.evals.length; k++) { + window.HTMLWidgets.evaluateStringMember(data.x, data.evals[k]); + } + binding.renderValue(el, data.x, initResult); + evalAndRun(data.jsHooks.render, initResult, [el, data.x]); + } + }); + }); + + invokePostRenderHandlers(); + } + + + function has_jQuery3() { + if (!window.jQuery) { + return false; + } + var $version = window.jQuery.fn.jquery; + var $major_version = parseInt($version.split(".")[0]); + return $major_version >= 3; + } + + /* + / Shiny 1.4 bumped jQuery from 1.x to 3.x which means jQuery's + / on-ready handler (i.e., $(fn)) is now asyncronous (i.e., it now + / really means $(setTimeout(fn)). + / https://jquery.com/upgrade-guide/3.0/#breaking-change-document-ready-handlers-are-now-asynchronous + / + / Since Shiny uses $() to schedule initShiny, shiny>=1.4 calls initShiny + / one tick later than it did before, which means staticRender() is + / called renderValue() earlier than (advanced) widget authors might be expecting. + / https://github.com/rstudio/shiny/issues/2630 + / + / For a concrete example, leaflet has some methods (e.g., updateBounds) + / which reference Shiny methods registered in initShiny (e.g., setInputValue). + / Since leaflet is privy to this life-cycle, it knows to use setTimeout() to + / delay execution of those methods (until Shiny methods are ready) + / https://github.com/rstudio/leaflet/blob/18ec981/javascript/src/index.js#L266-L268 + / + / Ideally widget authors wouldn't need to use this setTimeout() hack that + / leaflet uses to call Shiny methods on a staticRender(). In the long run, + / the logic initShiny should be broken up so that method registration happens + / right away, but binding happens later. + */ + function maybeStaticRenderLater() { + if (shinyMode && has_jQuery3()) { + window.jQuery(window.HTMLWidgets.staticRender); + } else { + window.HTMLWidgets.staticRender(); + } + } + + if (document.addEventListener) { + document.addEventListener("DOMContentLoaded", function() { + document.removeEventListener("DOMContentLoaded", arguments.callee, false); + maybeStaticRenderLater(); + }, false); + } else if (document.attachEvent) { + document.attachEvent("onreadystatechange", function() { + if (document.readyState === "complete") { + document.detachEvent("onreadystatechange", arguments.callee); + maybeStaticRenderLater(); + } + }); + } + + + window.HTMLWidgets.getAttachmentUrl = function(depname, key) { + // If no key, default to the first item + if (typeof(key) === "undefined") + key = 1; + + var link = document.getElementById(depname + "-" + key + "-attachment"); + if (!link) { + throw new Error("Attachment " + depname + "/" + key + " not found in document"); + } + return link.getAttribute("href"); + }; + + window.HTMLWidgets.dataframeToD3 = function(df) { + var names = []; + var length; + for (var name in df) { + if (df.hasOwnProperty(name)) + names.push(name); + if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") { + throw new Error("All fields must be arrays"); + } else if (typeof(length) !== "undefined" && length !== df[name].length) { + throw new Error("All fields must be arrays of the same length"); + } + length = df[name].length; + } + var results = []; + var item; + for (var row = 0; row < length; row++) { + item = {}; + for (var col = 0; col < names.length; col++) { + item[names[col]] = df[names[col]][row]; + } + results.push(item); + } + return results; + }; + + window.HTMLWidgets.transposeArray2D = function(array) { + if (array.length === 0) return array; + var newArray = array[0].map(function(col, i) { + return array.map(function(row) { + return row[i] + }) + }); + return newArray; + }; + // Split value at splitChar, but allow splitChar to be escaped + // using escapeChar. Any other characters escaped by escapeChar + // will be included as usual (including escapeChar itself). + function splitWithEscape(value, splitChar, escapeChar) { + var results = []; + var escapeMode = false; + var currentResult = ""; + for (var pos = 0; pos < value.length; pos++) { + if (!escapeMode) { + if (value[pos] === splitChar) { + results.push(currentResult); + currentResult = ""; + } else if (value[pos] === escapeChar) { + escapeMode = true; + } else { + currentResult += value[pos]; + } + } else { + currentResult += value[pos]; + escapeMode = false; + } + } + if (currentResult !== "") { + results.push(currentResult); + } + return results; + } + // Function authored by Yihui/JJ Allaire + window.HTMLWidgets.evaluateStringMember = function(o, member) { + var parts = splitWithEscape(member, '.', '\\'); + for (var i = 0, l = parts.length; i < l; i++) { + var part = parts[i]; + // part may be a character or 'numeric' member name + if (o !== null && typeof o === "object" && part in o) { + if (i == (l - 1)) { // if we are at the end of the line then evalulate + if (typeof o[part] === "string") + o[part] = tryEval(o[part]); + } else { // otherwise continue to next embedded object + o = o[part]; + } + } + } + }; + + // Retrieve the HTMLWidget instance (i.e. the return value of an + // HTMLWidget binding's initialize() or factory() function) + // associated with an element, or null if none. + window.HTMLWidgets.getInstance = function(el) { + return elementData(el, "init_result"); + }; + + // Finds the first element in the scope that matches the selector, + // and returns the HTMLWidget instance (i.e. the return value of + // an HTMLWidget binding's initialize() or factory() function) + // associated with that element, if any. If no element matches the + // selector, or the first matching element has no HTMLWidget + // instance associated with it, then null is returned. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.find = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var el = scope.querySelector(selector); + if (el === null) { + return null; + } else { + return window.HTMLWidgets.getInstance(el); + } + }; + + // Finds all elements in the scope that match the selector, and + // returns the HTMLWidget instances (i.e. the return values of + // an HTMLWidget binding's initialize() or factory() function) + // associated with the elements, in an array. If elements that + // match the selector don't have an associated HTMLWidget + // instance, the returned array will contain nulls. + // + // The scope argument is optional, and defaults to window.document. + window.HTMLWidgets.findAll = function(scope, selector) { + if (arguments.length == 1) { + selector = scope; + scope = document; + } + + var nodes = scope.querySelectorAll(selector); + var results = []; + for (var i = 0; i < nodes.length; i++) { + results.push(window.HTMLWidgets.getInstance(nodes[i])); + } + return results; + }; + + var postRenderHandlers = []; + function invokePostRenderHandlers() { + while (postRenderHandlers.length) { + var handler = postRenderHandlers.shift(); + if (handler) { + handler(); + } + } + } + + // Register the given callback function to be invoked after the + // next time static widgets are rendered. + window.HTMLWidgets.addPostRenderHandler = function(callback) { + postRenderHandlers.push(callback); + }; + + // Takes a new-style instance-bound definition, and returns an + // old-style class-bound definition. This saves us from having + // to rewrite all the logic in this file to accomodate both + // types of definitions. + function createLegacyDefinitionAdapter(defn) { + var result = { + name: defn.name, + type: defn.type, + initialize: function(el, width, height) { + return defn.factory(el, width, height); + }, + renderValue: function(el, x, instance) { + return instance.renderValue(x); + }, + resize: function(el, width, height, instance) { + return instance.resize(width, height); + } + }; + + if (defn.find) + result.find = defn.find; + if (defn.renderError) + result.renderError = defn.renderError; + if (defn.clearError) + result.clearError = defn.clearError; + + return result; + } +})(); diff --git a/docs/site_libs/plotly-binding-4.10.3/plotly.js b/docs/site_libs/plotly-binding-4.10.3/plotly.js new file mode 100644 index 00000000..7a2a143b --- /dev/null +++ b/docs/site_libs/plotly-binding-4.10.3/plotly.js @@ -0,0 +1,941 @@ + +HTMLWidgets.widget({ + name: "plotly", + type: "output", + + initialize: function(el, width, height) { + return {}; + }, + + resize: function(el, width, height, instance) { + if (instance.autosize) { + var width = instance.width || width; + var height = instance.height || height; + Plotly.relayout(el.id, {width: width, height: height}); + } + }, + + renderValue: function(el, x, instance) { + + // Plotly.relayout() mutates the plot input object, so make sure to + // keep a reference to the user-supplied width/height *before* + // we call Plotly.plot(); + var lay = x.layout || {}; + instance.width = lay.width; + instance.height = lay.height; + instance.autosize = lay.autosize || true; + + /* + / 'inform the world' about highlighting options this is so other + / crosstalk libraries have a chance to respond to special settings + / such as persistent selection. + / AFAIK, leaflet is the only library with such intergration + / https://github.com/rstudio/leaflet/pull/346/files#diff-ad0c2d51ce5fdf8c90c7395b102f4265R154 + */ + var ctConfig = crosstalk.var('plotlyCrosstalkOpts').set(x.highlight); + + if (typeof(window) !== "undefined") { + // make sure plots don't get created outside the network (for on-prem) + window.PLOTLYENV = window.PLOTLYENV || {}; + window.PLOTLYENV.BASE_URL = x.base_url; + + // Enable persistent selection when shift key is down + // https://stackoverflow.com/questions/1828613/check-if-a-key-is-down + var persistOnShift = function(e) { + if (!e) window.event; + if (e.shiftKey) { + x.highlight.persistent = true; + x.highlight.persistentShift = true; + } else { + x.highlight.persistent = false; + x.highlight.persistentShift = false; + } + }; + + // Only relevant if we haven't forced persistent mode at command line + if (!x.highlight.persistent) { + window.onmousemove = persistOnShift; + } + } + + var graphDiv = document.getElementById(el.id); + + // TODO: move the control panel injection strategy inside here... + HTMLWidgets.addPostRenderHandler(function() { + + // lower the z-index of the modebar to prevent it from highjacking hover + // (TODO: do this via CSS?) + // https://github.com/ropensci/plotly/issues/956 + // https://www.w3schools.com/jsref/prop_style_zindex.asp + var modebars = document.querySelectorAll(".js-plotly-plot .plotly .modebar"); + for (var i = 0; i < modebars.length; i++) { + modebars[i].style.zIndex = 1; + } + }); + + // inject a "control panel" holding selectize/dynamic color widget(s) + if ((x.selectize || x.highlight.dynamic) && !instance.plotly) { + var flex = document.createElement("div"); + flex.class = "plotly-crosstalk-control-panel"; + flex.style = "display: flex; flex-wrap: wrap"; + + // inject the colourpicker HTML container into the flexbox + if (x.highlight.dynamic) { + var pickerDiv = document.createElement("div"); + + var pickerInput = document.createElement("input"); + pickerInput.id = el.id + "-colourpicker"; + pickerInput.placeholder = "asdasd"; + + var pickerLabel = document.createElement("label"); + pickerLabel.for = pickerInput.id; + pickerLabel.innerHTML = "Brush color  "; + + pickerDiv.appendChild(pickerLabel); + pickerDiv.appendChild(pickerInput); + flex.appendChild(pickerDiv); + } + + // inject selectize HTML containers (one for every crosstalk group) + if (x.selectize) { + var ids = Object.keys(x.selectize); + + for (var i = 0; i < ids.length; i++) { + var container = document.createElement("div"); + container.id = ids[i]; + container.style = "width: 80%; height: 10%"; + container.class = "form-group crosstalk-input-plotly-highlight"; + + var label = document.createElement("label"); + label.for = ids[i]; + label.innerHTML = x.selectize[ids[i]].group; + label.class = "control-label"; + + var selectDiv = document.createElement("div"); + var select = document.createElement("select"); + select.multiple = true; + + selectDiv.appendChild(select); + container.appendChild(label); + container.appendChild(selectDiv); + flex.appendChild(container); + } + } + + // finally, insert the flexbox inside the htmlwidget container, + // but before the plotly graph div + graphDiv.parentElement.insertBefore(flex, graphDiv); + + if (x.highlight.dynamic) { + var picker = $("#" + pickerInput.id); + var colors = x.highlight.color || []; + // TODO: let users specify options? + var opts = { + value: colors[0], + showColour: "both", + palette: "limited", + allowedCols: colors.join(" "), + width: "20%", + height: "10%" + }; + picker.colourpicker({changeDelay: 0}); + picker.colourpicker("settings", opts); + picker.colourpicker("value", opts.value); + // inform crosstalk about a change in the current selection colour + var grps = x.highlight.ctGroups || []; + for (var i = 0; i < grps.length; i++) { + crosstalk.group(grps[i]).var('plotlySelectionColour') + .set(picker.colourpicker('value')); + } + picker.on("change", function() { + for (var i = 0; i < grps.length; i++) { + crosstalk.group(grps[i]).var('plotlySelectionColour') + .set(picker.colourpicker('value')); + } + }); + } + } + + // if no plot exists yet, create one with a particular configuration + if (!instance.plotly) { + + var plot = Plotly.newPlot(graphDiv, x); + instance.plotly = true; + + } else if (x.layout.transition) { + + var plot = Plotly.react(graphDiv, x); + + } else { + + // this is essentially equivalent to Plotly.newPlot(), but avoids creating + // a new webgl context + // https://github.com/plotly/plotly.js/blob/2b24f9def901831e61282076cf3f835598d56f0e/src/plot_api/plot_api.js#L531-L532 + + // TODO: restore crosstalk selections? + Plotly.purge(graphDiv); + // TODO: why is this necessary to get crosstalk working? + graphDiv.data = undefined; + graphDiv.layout = undefined; + var plot = Plotly.newPlot(graphDiv, x); + } + + // Trigger plotly.js calls defined via `plotlyProxy()` + plot.then(function() { + if (HTMLWidgets.shinyMode) { + Shiny.addCustomMessageHandler("plotly-calls", function(msg) { + var gd = document.getElementById(msg.id); + if (!gd) { + throw new Error("Couldn't find plotly graph with id: " + msg.id); + } + // This isn't an official plotly.js method, but it's the only current way to + // change just the configuration of a plot + // https://community.plot.ly/t/update-config-function/9057 + if (msg.method == "reconfig") { + Plotly.react(gd, gd.data, gd.layout, msg.args); + return; + } + if (!Plotly[msg.method]) { + throw new Error("Unknown method " + msg.method); + } + var args = [gd].concat(msg.args); + Plotly[msg.method].apply(null, args); + }); + } + + // plotly's mapbox API doesn't currently support setting bounding boxes + // https://www.mapbox.com/mapbox-gl-js/example/fitbounds/ + // so we do this manually... + // TODO: make sure this triggers on a redraw and relayout as well as on initial draw + var mapboxIDs = graphDiv._fullLayout._subplots.mapbox || []; + for (var i = 0; i < mapboxIDs.length; i++) { + var id = mapboxIDs[i]; + var mapOpts = x.layout[id] || {}; + var args = mapOpts._fitBounds || {}; + if (!args) { + continue; + } + var mapObj = graphDiv._fullLayout[id]._subplot.map; + mapObj.fitBounds(args.bounds, args.options); + } + + }); + + // Attach attributes (e.g., "key", "z") to plotly event data + function eventDataWithKey(eventData) { + if (eventData === undefined || !eventData.hasOwnProperty("points")) { + return null; + } + return eventData.points.map(function(pt) { + var obj = { + curveNumber: pt.curveNumber, + pointNumber: pt.pointNumber, + x: pt.x, + y: pt.y + }; + + // If 'z' is reported with the event data, then use it! + if (pt.hasOwnProperty("z")) { + obj.z = pt.z; + } + + if (pt.hasOwnProperty("customdata")) { + obj.customdata = pt.customdata; + } + + /* + TL;DR: (I think) we have to select the graph div (again) to attach keys... + + Why? Remember that crosstalk will dynamically add/delete traces + (see traceManager.prototype.updateSelection() below) + For this reason, we can't simply grab keys from x.data (like we did previously) + Moreover, we can't use _fullData, since that doesn't include + unofficial attributes. It's true that click/hover events fire with + pt.data, but drag events don't... + */ + var gd = document.getElementById(el.id); + var trace = gd.data[pt.curveNumber]; + + if (!trace._isSimpleKey) { + var attrsToAttach = ["key"]; + } else { + // simple keys fire the whole key + obj.key = trace.key; + var attrsToAttach = []; + } + + for (var i = 0; i < attrsToAttach.length; i++) { + var attr = trace[attrsToAttach[i]]; + if (Array.isArray(attr)) { + if (typeof pt.pointNumber === "number") { + obj[attrsToAttach[i]] = attr[pt.pointNumber]; + } else if (Array.isArray(pt.pointNumber)) { + obj[attrsToAttach[i]] = attr[pt.pointNumber[0]][pt.pointNumber[1]]; + } else if (Array.isArray(pt.pointNumbers)) { + obj[attrsToAttach[i]] = pt.pointNumbers.map(function(idx) { return attr[idx]; }); + } + } + } + return obj; + }); + } + + + var legendEventData = function(d) { + // if legendgroup is not relevant just return the trace + var trace = d.data[d.curveNumber]; + if (!trace.legendgroup) return trace; + + // if legendgroup was specified, return all traces that match the group + var legendgrps = d.data.map(function(trace){ return trace.legendgroup; }); + var traces = []; + for (i = 0; i < legendgrps.length; i++) { + if (legendgrps[i] == trace.legendgroup) { + traces.push(d.data[i]); + } + } + + return traces; + }; + + + // send user input event data to shiny + if (HTMLWidgets.shinyMode && Shiny.setInputValue) { + + // Some events clear other input values + // TODO: always register these? + var eventClearMap = { + plotly_deselect: ["plotly_selected", "plotly_selecting", "plotly_brushed", "plotly_brushing", "plotly_click"], + plotly_unhover: ["plotly_hover"], + plotly_doubleclick: ["plotly_click"] + }; + + Object.keys(eventClearMap).map(function(evt) { + graphDiv.on(evt, function() { + var inputsToClear = eventClearMap[evt]; + inputsToClear.map(function(input) { + Shiny.setInputValue(input + "-" + x.source, null, {priority: "event"}); + }); + }); + }); + + var eventDataFunctionMap = { + plotly_click: eventDataWithKey, + plotly_sunburstclick: eventDataWithKey, + plotly_hover: eventDataWithKey, + plotly_unhover: eventDataWithKey, + // If 'plotly_selected' has already been fired, and you click + // on the plot afterwards, this event fires `undefined`?!? + // That might be considered a plotly.js bug, but it doesn't make + // sense for this input change to occur if `d` is falsy because, + // even in the empty selection case, `d` is truthy (an object), + // and the 'plotly_deselect' event will reset this input + plotly_selected: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_selecting: function(d) { if (d) { return eventDataWithKey(d); } }, + plotly_brushed: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_brushing: function(d) { + if (d) { return d.range ? d.range : d.lassoPoints; } + }, + plotly_legendclick: legendEventData, + plotly_legenddoubleclick: legendEventData, + plotly_clickannotation: function(d) { return d.fullAnnotation } + }; + + var registerShinyValue = function(event) { + var eventDataPreProcessor = eventDataFunctionMap[event] || function(d) { return d ? d : el.id }; + // some events are unique to the R package + var plotlyJSevent = (event == "plotly_brushed") ? "plotly_selected" : (event == "plotly_brushing") ? "plotly_selecting" : event; + // register the event + graphDiv.on(plotlyJSevent, function(d) { + Shiny.setInputValue( + event + "-" + x.source, + JSON.stringify(eventDataPreProcessor(d)), + {priority: "event"} + ); + }); + } + + var shinyEvents = x.shinyEvents || []; + shinyEvents.map(registerShinyValue); + } + + // Given an array of {curveNumber: x, pointNumber: y} objects, + // return a hash of { + // set1: {value: [key1, key2, ...], _isSimpleKey: false}, + // set2: {value: [key3, key4, ...], _isSimpleKey: false} + // } + function pointsToKeys(points) { + var keysBySet = {}; + for (var i = 0; i < points.length; i++) { + + var trace = graphDiv.data[points[i].curveNumber]; + if (!trace.key || !trace.set) { + continue; + } + + // set defaults for this keySet + // note that we don't track the nested property (yet) since we always + // emit the union -- http://cpsievert.github.io/talks/20161212b/#21 + keysBySet[trace.set] = keysBySet[trace.set] || { + value: [], + _isSimpleKey: trace._isSimpleKey + }; + + // Use pointNumber by default, but aggregated traces should emit pointNumbers + var ptNum = points[i].pointNumber; + var hasPtNum = typeof ptNum === "number"; + var ptNum = hasPtNum ? ptNum : points[i].pointNumbers; + + // selecting a point of a "simple" trace means: select the + // entire key attached to this trace, which is useful for, + // say clicking on a fitted line to select corresponding observations + var key = trace._isSimpleKey ? trace.key : Array.isArray(ptNum) ? ptNum.map(function(idx) { return trace.key[idx]; }) : trace.key[ptNum]; + // http://stackoverflow.com/questions/10865025/merge-flatten-an-array-of-arrays-in-javascript + var keyFlat = trace._isNestedKey ? [].concat.apply([], key) : key; + + // TODO: better to only add new values? + keysBySet[trace.set].value = keysBySet[trace.set].value.concat(keyFlat); + } + + return keysBySet; + } + + + x.highlight.color = x.highlight.color || []; + // make sure highlight color is an array + if (!Array.isArray(x.highlight.color)) { + x.highlight.color = [x.highlight.color]; + } + + var traceManager = new TraceManager(graphDiv, x.highlight); + + // Gather all *unique* sets. + var allSets = []; + for (var curveIdx = 0; curveIdx < x.data.length; curveIdx++) { + var newSet = x.data[curveIdx].set; + if (newSet) { + if (allSets.indexOf(newSet) === -1) { + allSets.push(newSet); + } + } + } + + // register event listeners for all sets + for (var i = 0; i < allSets.length; i++) { + + var set = allSets[i]; + var selection = new crosstalk.SelectionHandle(set); + var filter = new crosstalk.FilterHandle(set); + + var filterChange = function(e) { + removeBrush(el); + traceManager.updateFilter(set, e.value); + }; + filter.on("change", filterChange); + + + var selectionChange = function(e) { + + // Workaround for 'plotly_selected' now firing previously selected + // points (in addition to new ones) when holding shift key. In our case, + // we just want the new keys + if (x.highlight.on === "plotly_selected" && x.highlight.persistentShift) { + // https://stackoverflow.com/questions/1187518/how-to-get-the-difference-between-two-arrays-in-javascript + Array.prototype.diff = function(a) { + return this.filter(function(i) {return a.indexOf(i) < 0;}); + }; + e.value = e.value.diff(e.oldValue); + } + + // array of "event objects" tracking the selection history + // this is used to avoid adding redundant selections + var selectionHistory = crosstalk.var("plotlySelectionHistory").get() || []; + + // Construct an event object "defining" the current event. + var event = { + receiverID: traceManager.gd.id, + plotlySelectionColour: crosstalk.group(set).var("plotlySelectionColour").get() + }; + event[set] = e.value; + // TODO: is there a smarter way to check object equality? + if (selectionHistory.length > 0) { + var ev = JSON.stringify(event); + for (var i = 0; i < selectionHistory.length; i++) { + var sel = JSON.stringify(selectionHistory[i]); + if (sel == ev) { + return; + } + } + } + + // accumulate history for persistent selection + if (!x.highlight.persistent) { + selectionHistory = [event]; + } else { + selectionHistory.push(event); + } + crosstalk.var("plotlySelectionHistory").set(selectionHistory); + + // do the actual updating of traces, frames, and the selectize widget + traceManager.updateSelection(set, e.value); + // https://github.com/selectize/selectize.js/blob/master/docs/api.md#methods_items + if (x.selectize) { + if (!x.highlight.persistent || e.value === null) { + selectize.clear(true); + } + selectize.addItems(e.value, true); + selectize.close(); + } + } + selection.on("change", selectionChange); + + // Set a crosstalk variable selection value, triggering an update + var turnOn = function(e) { + if (e) { + var selectedKeys = pointsToKeys(e.points); + // Keys are group names, values are array of selected keys from group. + for (var set in selectedKeys) { + if (selectedKeys.hasOwnProperty(set)) { + selection.set(selectedKeys[set].value, {sender: el}); + } + } + } + }; + if (x.highlight.debounce > 0) { + turnOn = debounce(turnOn, x.highlight.debounce); + } + graphDiv.on(x.highlight.on, turnOn); + + graphDiv.on(x.highlight.off, function turnOff(e) { + // remove any visual clues + removeBrush(el); + // remove any selection history + crosstalk.var("plotlySelectionHistory").set(null); + // trigger the actual removal of selection traces + selection.set(null, {sender: el}); + }); + + // register a callback for selectize so that there is bi-directional + // communication between the widget and direct manipulation events + if (x.selectize) { + var selectizeID = Object.keys(x.selectize)[i]; + var options = x.selectize[selectizeID]; + var first = [{value: "", label: "(All)"}]; + var opts = $.extend({ + options: first.concat(options.items), + searchField: "label", + valueField: "value", + labelField: "label", + maxItems: 50 + }, + options + ); + var select = $("#" + selectizeID).find("select")[0]; + var selectize = $(select).selectize(opts)[0].selectize; + // NOTE: this callback is triggered when *directly* altering + // dropdown items + selectize.on("change", function() { + var currentItems = traceManager.groupSelections[set] || []; + if (!x.highlight.persistent) { + removeBrush(el); + for (var i = 0; i < currentItems.length; i++) { + selectize.removeItem(currentItems[i], true); + } + } + var newItems = selectize.items.filter(function(idx) { + return currentItems.indexOf(idx) < 0; + }); + if (newItems.length > 0) { + traceManager.updateSelection(set, newItems); + } else { + // Item has been removed... + // TODO: this logic won't work for dynamically changing palette + traceManager.updateSelection(set, null); + traceManager.updateSelection(set, selectize.items); + } + }); + } + } // end of selectionChange + + } // end of renderValue +}); // end of widget definition + +/** + * @param graphDiv The Plotly graph div + * @param highlight An object with options for updating selection(s) + */ +function TraceManager(graphDiv, highlight) { + // The Plotly graph div + this.gd = graphDiv; + + // Preserve the original data. + // TODO: try using Lib.extendFlat() as done in + // https://github.com/plotly/plotly.js/pull/1136 + this.origData = JSON.parse(JSON.stringify(graphDiv.data)); + + // avoid doing this over and over + this.origOpacity = []; + for (var i = 0; i < this.origData.length; i++) { + this.origOpacity[i] = this.origData[i].opacity === 0 ? 0 : (this.origData[i].opacity || 1); + } + + // key: group name, value: null or array of keys representing the + // most recently received selection for that group. + this.groupSelections = {}; + + // selection parameters (e.g., transient versus persistent selection) + this.highlight = highlight; +} + +TraceManager.prototype.close = function() { + // TODO: Unhook all event handlers +}; + +TraceManager.prototype.updateFilter = function(group, keys) { + + if (typeof(keys) === "undefined" || keys === null) { + + this.gd.data = JSON.parse(JSON.stringify(this.origData)); + + } else { + + var traces = []; + for (var i = 0; i < this.origData.length; i++) { + var trace = this.origData[i]; + if (!trace.key || trace.set !== group) { + continue; + } + var matchFunc = getMatchFunc(trace); + var matches = matchFunc(trace.key, keys); + + if (matches.length > 0) { + if (!trace._isSimpleKey) { + // subsetArrayAttrs doesn't mutate trace (it makes a modified clone) + trace = subsetArrayAttrs(trace, matches); + } + traces.push(trace); + } + } + this.gd.data = traces; + } + + Plotly.redraw(this.gd); + + // NOTE: we purposely do _not_ restore selection(s), since on filter, + // axis likely will update, changing the pixel -> data mapping, leading + // to a likely mismatch in the brush outline and highlighted marks + +}; + +TraceManager.prototype.updateSelection = function(group, keys) { + + if (keys !== null && !Array.isArray(keys)) { + throw new Error("Invalid keys argument; null or array expected"); + } + + // if selection has been cleared, or if this is transient + // selection, delete the "selection traces" + var nNewTraces = this.gd.data.length - this.origData.length; + if (keys === null || !this.highlight.persistent && nNewTraces > 0) { + var tracesToRemove = []; + for (var i = 0; i < this.gd.data.length; i++) { + if (this.gd.data[i]._isCrosstalkTrace) tracesToRemove.push(i); + } + Plotly.deleteTraces(this.gd, tracesToRemove); + this.groupSelections[group] = keys; + } else { + // add to the groupSelection, rather than overwriting it + // TODO: can this be removed? + this.groupSelections[group] = this.groupSelections[group] || []; + for (var i = 0; i < keys.length; i++) { + var k = keys[i]; + if (this.groupSelections[group].indexOf(k) < 0) { + this.groupSelections[group].push(k); + } + } + } + + if (keys === null) { + + Plotly.restyle(this.gd, {"opacity": this.origOpacity}); + + } else if (keys.length >= 1) { + + // placeholder for new "selection traces" + var traces = []; + // this variable is set in R/highlight.R + var selectionColour = crosstalk.group(group).var("plotlySelectionColour").get() || + this.highlight.color[0]; + + for (var i = 0; i < this.origData.length; i++) { + // TODO: try using Lib.extendFlat() as done in + // https://github.com/plotly/plotly.js/pull/1136 + var trace = JSON.parse(JSON.stringify(this.gd.data[i])); + if (!trace.key || trace.set !== group) { + continue; + } + // Get sorted array of matching indices in trace.key + var matchFunc = getMatchFunc(trace); + var matches = matchFunc(trace.key, keys); + + if (matches.length > 0) { + // If this is a "simple" key, that means select the entire trace + if (!trace._isSimpleKey) { + trace = subsetArrayAttrs(trace, matches); + } + // reach into the full trace object so we can properly reflect the + // selection attributes in every view + var d = this.gd._fullData[i]; + + /* + / Recursively inherit selection attributes from various sources, + / in order of preference: + / (1) official plotly.js selected attribute + / (2) highlight(selected = attrs_selected(...)) + */ + // TODO: it would be neat to have a dropdown to dynamically specify these! + $.extend(true, trace, this.highlight.selected); + + // if it is defined, override color with the "dynamic brush color"" + if (d.marker) { + trace.marker = trace.marker || {}; + trace.marker.color = selectionColour || trace.marker.color || d.marker.color; + } + if (d.line) { + trace.line = trace.line || {}; + trace.line.color = selectionColour || trace.line.color || d.line.color; + } + if (d.textfont) { + trace.textfont = trace.textfont || {}; + trace.textfont.color = selectionColour || trace.textfont.color || d.textfont.color; + } + if (d.fillcolor) { + // TODO: should selectionColour inherit alpha from the existing fillcolor? + trace.fillcolor = selectionColour || trace.fillcolor || d.fillcolor; + } + // attach a sensible name/legendgroup + trace.name = trace.name || keys.join("
"); + trace.legendgroup = trace.legendgroup || keys.join("
"); + + // keep track of mapping between this new trace and the trace it targets + // (necessary for updating frames to reflect the selection traces) + trace._originalIndex = i; + trace._newIndex = this.gd._fullData.length + traces.length; + trace._isCrosstalkTrace = true; + traces.push(trace); + } + } + + if (traces.length > 0) { + + Plotly.addTraces(this.gd, traces).then(function(gd) { + // incrementally add selection traces to frames + // (this is heavily inspired by Plotly.Plots.modifyFrames() + // in src/plots/plots.js) + var _hash = gd._transitionData._frameHash; + var _frames = gd._transitionData._frames || []; + + for (var i = 0; i < _frames.length; i++) { + + // add to _frames[i].traces *if* this frame references selected trace(s) + var newIndices = []; + for (var j = 0; j < traces.length; j++) { + var tr = traces[j]; + if (_frames[i].traces.indexOf(tr._originalIndex) > -1) { + newIndices.push(tr._newIndex); + _frames[i].traces.push(tr._newIndex); + } + } + + // nothing to do... + if (newIndices.length === 0) { + continue; + } + + var ctr = 0; + var nFrameTraces = _frames[i].data.length; + + for (var j = 0; j < nFrameTraces; j++) { + var frameTrace = _frames[i].data[j]; + if (!frameTrace.key || frameTrace.set !== group) { + continue; + } + + var matchFunc = getMatchFunc(frameTrace); + var matches = matchFunc(frameTrace.key, keys); + + if (matches.length > 0) { + if (!trace._isSimpleKey) { + frameTrace = subsetArrayAttrs(frameTrace, matches); + } + var d = gd._fullData[newIndices[ctr]]; + if (d.marker) { + frameTrace.marker = d.marker; + } + if (d.line) { + frameTrace.line = d.line; + } + if (d.textfont) { + frameTrace.textfont = d.textfont; + } + ctr = ctr + 1; + _frames[i].data.push(frameTrace); + } + } + + // update gd._transitionData._frameHash + _hash[_frames[i].name] = _frames[i]; + } + + }); + + // dim traces that have a set matching the set of selection sets + var tracesToDim = [], + opacities = [], + sets = Object.keys(this.groupSelections), + n = this.origData.length; + + for (var i = 0; i < n; i++) { + var opacity = this.origOpacity[i] || 1; + // have we already dimmed this trace? Or is this even worth doing? + if (opacity !== this.gd._fullData[i].opacity || this.highlight.opacityDim === 1) { + continue; + } + // is this set an element of the set of selection sets? + var matches = findMatches(sets, [this.gd.data[i].set]); + if (matches.length) { + tracesToDim.push(i); + opacities.push(opacity * this.highlight.opacityDim); + } + } + + if (tracesToDim.length > 0) { + Plotly.restyle(this.gd, {"opacity": opacities}, tracesToDim); + // turn off the selected/unselected API + Plotly.restyle(this.gd, {"selectedpoints": null}); + } + + } + + } +}; + +/* +Note: in all of these match functions, we assume needleSet (i.e. the selected keys) +is a 1D (or flat) array. The real difference is the meaning of haystack. +findMatches() does the usual thing you'd expect for +linked brushing on a scatterplot matrix. findSimpleMatches() returns a match iff +haystack is a subset of the needleSet. findNestedMatches() returns +*/ + +function getMatchFunc(trace) { + return (trace._isNestedKey) ? findNestedMatches : + (trace._isSimpleKey) ? findSimpleMatches : findMatches; +} + +// find matches for "flat" keys +function findMatches(haystack, needleSet) { + var matches = []; + haystack.forEach(function(obj, i) { + if (obj === null || needleSet.indexOf(obj) >= 0) { + matches.push(i); + } + }); + return matches; +} + +// find matches for "simple" keys +function findSimpleMatches(haystack, needleSet) { + var match = haystack.every(function(val) { + return val === null || needleSet.indexOf(val) >= 0; + }); + // yes, this doesn't make much sense other than conforming + // to the output type of the other match functions + return (match) ? [0] : [] +} + +// find matches for a "nested" haystack (2D arrays) +function findNestedMatches(haystack, needleSet) { + var matches = []; + for (var i = 0; i < haystack.length; i++) { + var hay = haystack[i]; + var match = hay.every(function(val) { + return val === null || needleSet.indexOf(val) >= 0; + }); + if (match) { + matches.push(i); + } + } + return matches; +} + +function isPlainObject(obj) { + return ( + Object.prototype.toString.call(obj) === '[object Object]' && + Object.getPrototypeOf(obj) === Object.prototype + ); +} + +function subsetArrayAttrs(obj, indices) { + var newObj = {}; + Object.keys(obj).forEach(function(k) { + var val = obj[k]; + + if (k.charAt(0) === "_") { + newObj[k] = val; + } else if (k === "transforms" && Array.isArray(val)) { + newObj[k] = val.map(function(transform) { + return subsetArrayAttrs(transform, indices); + }); + } else if (k === "colorscale" && Array.isArray(val)) { + newObj[k] = val; + } else if (isPlainObject(val)) { + newObj[k] = subsetArrayAttrs(val, indices); + } else if (Array.isArray(val)) { + newObj[k] = subsetArray(val, indices); + } else { + newObj[k] = val; + } + }); + return newObj; +} + +function subsetArray(arr, indices) { + var result = []; + for (var i = 0; i < indices.length; i++) { + result.push(arr[indices[i]]); + } + return result; +} + +// Convenience function for removing plotly's brush +function removeBrush(el) { + var outlines = el.querySelectorAll(".select-outline"); + for (var i = 0; i < outlines.length; i++) { + outlines[i].remove(); + } +} + + +// https://davidwalsh.name/javascript-debounce-function + +// Returns a function, that, as long as it continues to be invoked, will not +// be triggered. The function will be called after it stops being called for +// N milliseconds. If `immediate` is passed, trigger the function on the +// leading edge, instead of the trailing. +function debounce(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) func.apply(context, args); + }; +}; diff --git a/docs/sitemap.xml b/docs/sitemap.xml index 55f83d58..681626f9 100644 --- a/docs/sitemap.xml +++ b/docs/sitemap.xml @@ -10,7 +10,7 @@ https://www.spsanderson.com/steveondata/index.html - 2023-11-15T13:40:04.468Z + 2023-11-16T13:30:59.617Z https://www.spsanderson.com/steveondata/posts/rtip-2023-04-06/index.html @@ -568,4 +568,8 @@ https://www.spsanderson.com/steveondata/posts/2023-11-15/index.html 2023-11-15T13:40:12.254Z + + https://www.spsanderson.com/steveondata/posts/2011-11-16/index.html + 2023-11-16T13:31:08.602Z + diff --git a/posts/2011-11-16/index.qmd b/posts/2011-11-16/index.qmd new file mode 100644 index 00000000..f860467d --- /dev/null +++ b/posts/2011-11-16/index.qmd @@ -0,0 +1,108 @@ +--- +title: "{healthyR.ts} New Features: Unlocking More Power" +author: "Steven P. Sanderson II, MPH" +date: "2023-11-16" +categories: [rtip, healthyrts, timeseries] +--- + +# New Features: Unlocking More Power + +My R package `{healthyR.ts}` has been updated to version 0.3.0; you can install it from either CRAN, r-universe or GitHub. Let's go over some of the changes and improvements. + +# News + +## 1. `util_log_ts()` - Logging Time Series Data + +One of the standout additions is the introduction of `util_log_ts()`. This function seems like a game-changer, providing a streamlined way to log time series data. This is incredibly useful, especially when dealing with extensive datasets, making the whole process more efficient and user-friendly. This is a helper function for `auto_stationarize()`. + +## 2. `util_singlediff_ts()` - Single Differences for Time Series + +The addition of `util_singlediff_ts()` expands the toolkit, offering a function dedicated to handling single differences in time series data. This is valuable for various applications, such as identifying trends or preparing data for further analysis. This is a helper function for `auto_stationarize()`. + +## 3. `util_doublediff_ts()` - Double Differences for Time Series + +Building on the concept of differencing, `util_doublediff_ts()` seems to provide a higher level of sophistication, allowing users to perform double differences on time series data. This could be pivotal in cases where a more refined analysis is required. This is a helper function for `auto_stationarize()`. + +## 4. `util_difflog_ts()` - Combining Differences and Log Transformation + +The fusion of differencing and log transformation in `util_difflog_ts()` is a remarkable addition. This could be particularly beneficial in scenarios where both operations are needed to unlock deeper insights from the time series data. This is a helper function for `auto_stationarize()`. + +## 5. `util_doubledifflog_ts()` - Double Differences with Log Transformation + +The introduction of `util_doubledifflog_ts()` appears to take things a step further by combining double differences and log transformation. This function seems poised to provide a comprehensive solution for users dealing with complex time series data. This is a helper function for `auto_stationarize()`. + +# Minor Fixes and Improvements: Polishing the Experience + +## 1. Attributes Enhancement in `ts_growth_rate_vec()` +The attention to detail is evident with the addition of attributes to the output of `ts_growth_rate_vec()`. This enhancement not only improves the clarity of results but also contributes to a more informative and user-friendly experience. + +## 2. Refinement of `auto_stationarize()` in Response to User Feedback + +Updates to `auto_stationarize()` based on user feedback (Fix #481 #483) demonstrate a commitment to refining existing features. This responsiveness to the community's needs is commendable and ensures that the package evolves in sync with user expectations. It has taken all of the `util_` transforms mentioned above in order to improve it's functionality. + +## 3. Integration with `auto_arima` Engine in `ts_auto_arima()` + +The integration of `ts_auto_arima()` with the parsnip engine of `auto_arima` is a notable improvement. This update, triggered when `.tune` is set to `FALSE`, aligns the package with cutting-edge tools, potentially enhancing the efficiency and accuracy of time series modeling. + +In conclusion, the release of healthyR.ts version 0.3.0 is an exciting leap forward. The new features introduce powerful capabilities, while the minor fixes and improvements showcase a commitment to providing a robust and user-friendly package. Users can look forward to a more versatile and refined experience in time series analysis. Great job on this release, and I'm sure the community is eager to explore these enhancements! + +# Examples + +Let's see how the main functions now behave. + +## `auto_stationarize()` + +```{r message=FALSE, warning=FALSE} +library(healthyR.ts) + +auto_stationarize(AirPassengers) +auto_stationarize(BJsales) +plot.ts(auto_stationarize(BJsales)$stationary_ts) +auto_stationarize(BJsales.lead) +plot.ts(auto_stationarize(BJsales.lead)$stationary_ts) +``` + +## `ts_auto_arima()` + +This use to only use the Arima engine if the `.tune` parameter was set to FALSE, thus it would many times give a simple straight line forecast. This was changed to make the engine auto_arima if `.tune` is set to FALSE. + +```{r message=FALSE, warning=FALSE} +library(timetk) +library(dplyr) +library(modeltime) + +data <- AirPassengers |> + ts_to_tbl() |> + select(-index) + +splits <- time_series_split( + data + , date_col + , assess = 12 + , skip = 3 + , cumulative = TRUE +) + +ts_aa <- ts_auto_arima( + .data = data, + .num_cores = 2, + .date_col = date_col, + .value_col = value, + .rsamp_obj = splits, + .formula = value ~ ., + .grid_size = 5, + .cv_slice_limit = 2, + .tune = FALSE +) + +ts_aa$recipe_info +ts_aa$model_info +ts_aa$model_calibration +ts_aa$model_calibration$plot +``` + +Finally enhancement to add attributes to `ts_growth_rate_vec()` + +```{r} +ts_growth_rate_vec(AirPassengers) +``` \ No newline at end of file