# qol 1.3.2

### New functions

* `code_statistics()`: Reads in a folder or entire folder structure, grabs all R script files, scans the contents for different patterns and outputs a small report. (04.06.2026)

### Renamed functions

* `retain_stat()`: `retain_sum` got a new name because it can now generate any statistic not only sums with the new `statistics` parameter. (11.06.2026)
* `set_labels()`, `get_labels()`: The global setters and getters for variable and statistic labels `set_variable_labels()`, `get_variable_labels()` and `set_stat_labels()`, `get_stat_labels()` have been put together in one function. (12.06.2026)

### New functionality

* `running_number()`: With the new `sort` parameter a vektor of variable names can be passed as `by`, which then is automatically sorted before generating the running number. There is also a new `group_nr` parameter which creates a running number for each group as a whole instead of a running number within the group. (26.05.2026)
* `concat()`: Can now concatenate variables inside a data frame with a separator. (03.06.2026)
* `recode_multi()`: New `convert` parameter converts recoded variables to numeric or character depending on the input format instead of leaving them as factors. (03.06.2026)
* `any_table()`, `excel_output_style()`: Can now apply a `background_color` to all cells which aren't covered by any other background color parameter. (03.06.2026)
* `frequencies()`: The `number format` option `sum_decimals` within the `excel_output_style()` function can now be used to set the decimal places in text outputs as well. (04.06.2026)
* `libname()`: With the new `recursive` parameter files can now also be retreived from subfolders. (04.06.2026)
* `export_with_style()`: Can now apply a `background_color` to all cells which aren't covered by any other background color parameter. (11.06.2026)
* `export_with_style()`: Added a new `column_align` parameter with which every column alignment can be controlled individually. (11.06.2026)

### Changed functionality

* `compute.()`: Now handles calls sequential, so that a new variable generated within `compute.()` can directly be accesses and used within the same `compute.()` call. (13.06.2026)
* `compute.()`: In a do over loop, if different values are assigned to the same variable, then now the last value is assigned instead of the first. (16.06.2026)
* `summarise_plus()`: Percentiles are now calculated the SAS way, which is Type 2 described in `quantile()`. (16.06.2026)

### Fixed

* `if.()`, `else_if.()`, `else.()`: Do over loop couldn't handle logical vectors. Additionally character expresions where always converted to symbols, which was fine if variable names were meant, but crashed if actual character values were passed. This is fixed now. (01.06.2026)
* `print_step()`: Doesn't print empty lines anymore, when surrounding the function with `suppressMessages()`. (01.06.2026)
* `sort_plus()`: Doesn't crash anymore when trying to preserve factor variables. (03.06.2026)
* `export_data()`: When exporting a csv file characters like ä, ö, ü are displayed correctly, when opening the file in Excel. (03.06.2026)
* `libname()`: When using the `extensions` parameter then now only files with these extensions are printed out to the console. Otherwise a `NOTE` is displayed stating that no file has been found. (04.06.2026)
* `if.()`, `else_if.()`: If a colon ":" was written somewhere in a character expression inside a condition the function crashed because it was looking for the return value of the special ":" symbol. This is fixed now. (09.06.2026)
* `export_with_style()`: Function doesn't crash anymore, if a variable label is provided, but the corresponding variable doesn't exist in the data frame. (11.06.2026)
* `dummy_data()`: Variable `number_of_persons` now has the right values. Before it was generated too early. (11.06.2026)
* `compute.()`, `if.()`, `else_if.()`, `else.()`: If do-over-loop behavior is combined with normal variable generation, then the do over loop now doesn't run for the normal variable generation and condition set up anymore. (13.06.2026)
* `any_table()`: The statistic `freq_g0` couldn't be renamed correctly. It can now be renamed by just doing `stat_labels = list("freq" = "Some label")`. (15.06.2026)
* `if.()`, `else_if.()`, `else.()`: If the variable to which a value should be assigned already existed in the data frame, then the existing values were overwritten with NA, if there were NA values in a variable that was used in the condition. Values are now preserved. (16.06.2026)

### Optimization

* `if.()`: Got rid of an unnecessary loop. (13.06.2026)
* `print_step()`, `print_start_message()`, `print_headline()`, `print_closing()`: Execution token is now only retrieved, if the print function is called from a low depth. Meaning deeply nested print statements wont retrieve the token because this becomes very resource heavy. (13.06.2026)
* `summarise_plus()`: Tackled a performance nightmare in the core summarisation function. Basically every statistic operation is now up to 10+ times faster than before and uses less than half the memory. This also has an impact on all the tabulation functions. (14.06.2026)
* `any_table()`: The cell merging for the column and row headers was handled range by range, meaning a merge per range. Now the ranges to be merged are collected first and then merged in one go. This gives a massive performance gain for larger tables. (15.06.2026)

### Additionally

* `if.()`, `export_with_style()`: Updated examples. (19.05.2026)
* `summarise_plus()`, `drop_type_vars()`: Updated documentation with a detailed description of the automatically generated variables TYPE, TYPE_NR and DEPTH when using the `nesting = "all"` or `nesting = "single"` option. (27.05.2026, thanks to @cyklee)
* `expand_formats()`: Added a `NOTE`message if the cartesian product produces over 1 million rows. (02.06.2026)
* `any_table()`: Added two new `NOTE` messages in case `pct_value` or `pct_block` is specified in statistics put corresponding parameter isn't set. In this case the statistic will be removed. (08.06.2026)
* `export_with_style()`: Added some more messages informing the user about the formatting timings. (11.06.2026)
* Global options documentation received some more comments. Additionally the functions themselves received some messages stating whether options have been set. Also the getters which retrieve a list have a nicer output. (12.06.2026)

# qol 1.3.1

CRAN release on 16.05.2026

### Renamed functions

* `compute.()`, `recode.()`: Both functions have been renamed and now have a "." at the end to prevent masking errors in combination with `dplyr`. (28.04.2026)

### New functionality

* `if.()`, `else_if.()`: The ":" can now be used in conditions as a placeholder for "starts with" ("text:"), "ends with" (":text") and "contains" (":text:"). (21.03.2026)
* `set_no_color()`: Suppresses the color codes so that messages can be printed clean. The option is auto controlled on load via the system variable `NO_COLOR` but can also be set individually by this function. Console output in e.g. RStudio vs. output to a logging system should be handled automatically rightnow. (25.04.2026, thanks to @TroyHernandez)
* `set_up_custom_message()`: Waiting symbols as well as the color of the time stamps can now be customized. (25.04.2026)
* `print_step()`: Now has a new `in_place` parameter, which prints the message on the same line as before, instead of in the next line. This can e.g. be used inside loops as follows. (29.04.2026)
* `any_table()`, `export_with_style()`: If the whole result list from these functions is passed for the `workbook` parameter, the functions now are able to extract the actual workbook from the list and run without error. Additionally if a list is passed, which is not a result list containing the workbook, the functions error and abort execution. (05.05.2026)
* `transpose_plus()`: In a wide to long transposition it is now possible to not only put results below each other, but also side by side. (11.05.2026)
* `any_table()`, `frequencies()`, `crosstabs()`: If 'csv' is specified as extension in the `file name` set in the global options or the style parameter the result table will then be exported as 'csv'. Otherwise the actual workbook will be exported as `xlsx` as normal. (13.05.2026)
* `if.()`: If the new `delete` keyword is passed instead of a variable assignment, the provided condition deletes observations instead of keeping them. (15.05.2026)

### Changed functionality

* `set_up_custom_message()`: The way custom messages are set up has slightly changed and will break existing code. Custom message types are now stored globally and are called within quotation marks like all the other built-in message types. See updated README for the new handling. (25.04.2026)
* `interval_format()`: The function had a flaw in that it always included the lower and upper bound values. The "workaround" in the examples to pass bounds like e.g. 0:499, 500:999, would miss values between 499 and 500 or 999 and 1000. The function now got two new parameters `inlcude_lower` (TRUE) and `include_upper` (FALSE), which by default read as "from ... to under ...". Which means bounds can be passed like 0:500, 500:1000 without overlapping. (07.05.2026)
* `recode.()`: Interval formats can now be applied even though a variable has NA values. NA values are taken care of automatically instead of the function aborting with an error. Additionally multilabels are reduced so that no additional observations are created. (08.05.2026)

### Fixed

* `combine_into_workbook()`: Titles and footnotes are now styled again. (18.04.2026)
* `compute.()`: If a variable was all NA, a type miss match happend, leading to the function beeing aborted. This is fixed now. (24.04.2026)
* `print_step()`: Waiting character "?" is now drawn as intended in non-utf8 mode. (25.04.2026)
* `compute.()`, `if.()`, `else_if.()`, `else.()`: Now detect vectors for do-over-loops in every place. Previously do over loop was only detected, if the variable for assignment was a vector. (30.04.2026)
* `compute.()`: Doesn't crash anymore, if multiple values are assigned to the same variable in a do over loop. (30.04.2026)
* `dummy_data()`: Variable `income_class` was miss matched in some places due to it being generated to early. This is fixed now. (30.04.2026)
* `any_table()`: If the `block` keyword was used in `var_labels` parameter and all `block` keywords weren't written in lower case, the column header generation crashed. This is fixed now. (04.05.2026)
* `interval_format()`: Now preserves single values. (08.05.2026)
* `dummy_data()`: `person_id` is now adjusted after taking sample to have continuous numbers. (08.05.2026)

### Additionally

* Message time stamps are now drawn in a lighter grey to enhance visibility on darker themes. (20.04.2026)
* `else_do()`, `end_do()`, `end_all_do()`: Functions now throw errors instead of warnings, if `do_if()` is missing. (23.04.2026)
* `any_table()`, `frequencies()`, `crosstabs()`, `export_with_style()`: If only the `save_path` or the `file name` is specified in the global options or the style parameter there will now be a note stating that something is missing. (13.05.2026)
* `if.()`, `else_if.()`: `&&` and `||` will now be converted into `&` and `|`. Additionally a warning is displayed. (15.05.2026)
* Set the used threads from data.table to 2 for the `summarise_plus()` examples, because the CRAN server throws a NOTE for the r-devel-linux-x86_64-debian-gcc: "Examples with CPU time > 2.5 times elapsed time". (16.05.2026)

# qol 1.3.0

CRAN release on 18.04.2026

### New functions

* `row_calculation()`: Perform row wise calculations on numeric variables. (12.03.2026)
* `do_if()`, `else_do()`, `end_do()`, `end_all_do()`: These functions create overarching filter variables. When used with functions capable of handling these filter variables, it is possible to create more readable if-blocks without writing the same condition over and over again. (21.03.2026)
* `compute.()`: Compute new variables without having to write the name of the data frame multiple times. (23.03.2026)
* `save_file()`, `save_file_multi()`: Saves fst and rds files. Offers variable selection and observation subsetting. By default the function has a write protection, which has to be explicitly turned off to be able to overwrite files. (28.03.2026)
* `load_file()`, `load_file_multi()`: Loads fst and rds files. Provided variables to keep are read in case insensitive and are returned in provided order. Additionally a subset can be defined directly. Loaded files in the multi version can be stacked or output as a list. (28.03.2026)
* `set_threads()`, `get_threads()`: Globally sets/gets the number of used threads for the save and load file functions. (28.03.2026)
* Base R message system:
	* `print_message()`, `print_headline()`, `print_start_message()`, `print_closing()`, `print_step()`: Print out different messages with custom styling to the console. (09.04.2026)
	* `get_message_stack()`: Get the global message stack for inspection. (09.04.2026)
	* `set_no_print()`: Prevents messages being printed to the console globally. Can e.g. be used in unit test situations. (09.04.2026)
	* `print_stack_as_messages()`: Print the global message stack as message, warning or error to be able to receive their signals. (09.04.2026)
	* `convert_square_brackets()`: Transforms the format symbols (like {b}{/b}) into the actual console readable formattings. (09.04.2026)
	* `set_up_custom_message()`: Sets up the basic items for a custom message. These custom types can be input into `print_message()` and `print_step()`. (13.04.2026)
* `hex_to_256()`, `hex_to_ansi()`: Generate a 256-color 6x6x6 color cube and apply hex color and font weight to a text as ansi codes. (09.04.2026)
* `round_multi()`: Rounds multiple variables at once inside a data frame. (09.04.2026)
* `report_test_results()`, `test_package()`, `test_single_file()`: Print out tinytest results with custom reporting style. (15.04.2026)

### New functionality

* `if.()`, `else_if.()`: The ":" can now be used in conditions as a placeholder for "starts with" ("text:"), "ends with" (":text") and "contains" (":text:"). (21.03.2026)
* `if.()`, `else_if.()`, `else.()`: All functions are now able to handle vectors in a do over loop. Meaning, that if using vectors in conditions or assignments, the functions will iterate over each vector simultaneously. (21.03.2026)
* `if.()`, `else_if.()`, `else.()`: Can be used within the new `do_if()` context. (21.03.2026)
* `if.()`, `else_if.()`, `else.()`: Since the functions now make use of the new `compute.()`, they are able to do more than just simple variable assignments and can also perform calculations. (23.03.2026)
* `row_calculation()`: Now has an optional rounding parameter. (23.03.2026)
* `dummy_data()`: Added `weight_per_year` variable. (25.03.2026)
* `build_master()`: Added `with_monitor` parameter, which allows to visualize the time consumption of the individual scripts. (26.03.2026)
* `split_by()`: Now also outputs NA values. (29.03.2026)
* `import_data()`, `import_multi()`, `export_data()`, `export_multi()`: Now can import and export csv files with file extension txt. (30.03.2026)
* `round_values()`: Now is also able to round in multiples of a provided value. (09.04.2026)
* `combine_into_workbook()`: Now can handle `export_with_style()` results. (10.04.2026)
* `excel_output_style()`: Now accepts multiple title and footnote colors, sizes and boldings. (10.04.2026)
* `excel_output_style()`: New option `header_stat_merging` determines how the statistic symbols or labels are merged in the column header. As a new standard the labels are now not fully merged but in blocks. (10.04.2026)
* `if.()`: Observation selection now also works as do over loop. (12.04.2026)
* `any_table()`: New parameter `pct_block` enables percentage calculation inside the respective second to last grouping of the row or column variables. (12.04.2026)
* `any_table()`: If the result expression of a format starts with an "!", this expression will be used in calculations but will be dropped in the result table. This can be used to calculate block percentages based on a total expression without showing the total in the result table. (12.04.2026)
* `any_table()`: Variable combinations in rows and columns can now also be passed like "state + (age sex education)", which results in "state + age", "state + sex", "state + education", with the addition that the results with the same root grouping will be sorted together concerning the row header variables. For the column sorting there is a new `order_by` option with "blocks" for that purpose. (12.04.2026)
* `any_table()`: There are new special `var_labels`, which add additional top column headers. When assigning labels to variables that start with "block", e.g. "block1" = "Column Percentages", "block2" = "Row Percentages", these labels will appear as the top line of the column header. (12.04.2026)
* `summarise_plus()`: Variable combinations in types can now also be passed like "state + (age sex education)", which results in "state + age", "state + sex", "state + education". (15.04.2026)
* `frequencies()`, `crosstabs()`, `any_table()`, `export_with_style()`: Titles and footnotes can now also link to cells "cell:" and to files "file:". (15.04.2026)
* `libname()`: Can now optionally filter file list by provided file extensions. (16.04.2026)
* `set_style_options()`: Can now save the currently set global options to a rds file. (16.04.2026)
* `get_style_options()`: Can now load a rds file and set the contents as global style options. (16.04.2026)

### Changed functionality

* `summarise_plus()`: Instead of aborting when no values are passed, the function now generates a variable to output unweighted results. (26.03.2026)
* `Unit tests`: Moved unit tests completely from `testthat` to `tinytest`. `testthat` repeatedly got in the way with its own environment, `tinytest` offers more natural conditions and works faster. (31.01.2026)
* `transpose_plus()`: Removed macro variable usage. (09.04.2026)
* `export_with_style()`: Now returns a list with table, workbook and meta information, which can be used with `combine_into_workbook()`. (10.04.2026)
* `build_master()`: Now only catches R-script files and ignores files of all other types. (16.04.2026)

### Fixed

* `any_table()`: Some examples had a too long running time. Additionally shortened the running time of unit tests by ignoring styling options in the larger tables. (11.03.2026)
* `any_table()`: Fixed percentages based on single formatted variable expressions couldn't be computed with by variables. (11.03.2026)
* `any_table()`: The column header wasn't put together correctly, if only multiple group percentages were computed. (11.03.2026)
* `any_table()`: Excel number stored as text error is now ignored in general even if no styling is used. This was especially noticeable when generating tables with by variables and the `print_miss` option. (11.03.2026)
* `any_table()`: Even if no styling is used the tables now all receive the named regions. (11.03.2026)
* `any_table()`: Columns are now sorted correctly, if only row or column percentages are computed. (11.03.2026)
* `dummy_data()`: Now cuts down observations by random sample instead of just the head, so that the cut expressions aren't lost completely. (11.03.2026)
* `dummy_data()`: `balance` variable wasn't calculated correctly, which is now fixed. (12.03.2026)
* `any_table()`, `combine_into_workbook()`: The `output` parameter wasn't stored in the meta information list which lead to `combine_into_workbook()` not able to access it and always output formatted tables. Now the `output` parameter is stored in meta information. (24.03.2026)
* `build_master()`: Now ignores the root folder when building the script, to enable automatic rebuilding of the script. (26.03.2026)
* `import_data()`: In the examples the file names to read in weren't correct. This is fixed now. (28.03.2026)
* `split_by()`: Should now work inside custom functions. (29.03.2026)
* `retain_value()`, `retain_sum()`: On error now return NA instead of the whole data frame. (31.01.2026)
* `transpose_plus()`: Wide to long transposition now working again as intended. (09.04.2026)
* `any_table()`: Without column variables each variable in the column header received a "_ " at the end, which is not the case anymore. (10.04.2026)
* `sort_plus()`: When formats where used for sorting, the temporary sorting variables where always put up front instead of inserted in sequential order. This is fixed now. (10.04.2026)
* `keep()`, `dropp()`: If a character pattern is used to keep or drop variables and no variable matching the pattern is found, the functions now abort with a warning. (12.04.2026)
* `where.()`: Now aborts with a warning if no observations or variables are left in the data frame. (13.04.2026)
* `export_with_style()`: Can now rename variables that have blanks in their name. (13.04.2026)
* `summarise_plus()`, `frequencies()`, `crosstabs()`, `any_table()`: If an invalid format is passed, this now throws a warning instead of removing all formats silently. (14.04.2026)

### Optimization

* `if.()`, `else_if.()`, `else.()`: With the new `compute.()` they now perform variable assignments and calculations in a single pass instead of per iteration. (23.03.2026)
* `running_number()`: Now uses faster `data.table` function. (23.03.2026)
* `round_values()`: Now uses vectorized math to reduce memory allocation. (23.03.2026)
* `dummy_data()`: Reduced memory allocation. (25.03.2026)
* `summarise_plus()`: When using factor or character variables in `class` parameter, the check to preserve "." is now executed on the unique values first and only mapped back to the data frame, if there are any replacements needed, instead of running a `gsub` every time over the whole vector. (28.03.2026)
* `import_data()`, `import_multi()`: `import_data()` now also accepts a `openxlsx2` workbook as infile and not only a path to a workbook. This enables a massive performance boost for `import_multi()`, which now passes a workbook to `import_data()` instead of the file path. (28.03.2026)

### Additionally

* `any_table()`: `statistics` parameter is now NULL by default to enable group percentages to be computed on their own without sums. (11.03.2026)
* Swapped out a variable in abort `split_by()` because of duplicates test, because the test didn't throw the expected error. This was a very rare error because of bad luck with the dummy_data sample. The function works as intended. (23.03.2026)
* `any_table()`: Upped the dummy_data observations in tests to prevent rare errors. (23.03.2026)
* `any_table()`: Omitted table formatting in some tests to make them run quicker. (24.03.2026)
* `any_table()`, `frequencies()`, `crosstabs()`, `export_with_style()`: Added missing global options function in the `see also` section. (25.03.2026)
* `import_multi()`, `export_multi()`: Added some messages to show progress. (28.03.2026)
* Added repository to DeepWiki and added badge to README. (29.03.2026)
* `any_table()`, `export_with_style()`: Added examples for `combine_into_workbook()`. (10.04.2026)
* `any_table()`: When row header labels are suppressed, the excess slashes are now removed. (10.04.2026)
* `compute.()`: Now throws a warning, if duplicate variable names are used, but goes on computing the valid variables. (15.04.2026)

# qol 1.2.2

CRAN release on 10.03.2026

### New functions

* `round_values()`: Rounds values according to the round half up rule. (20.02.2026)

### New functionality

* `any_table()`: Added `row_pct` and `col_pct` keywords to `pct_group` parameter. With this the function can calculate total percentages for rows and columns regardless of the respective other dimension. (21.02.2026)
* `rename_multi()`: Variable names can now be passed without quotation marks. (28.02.2026)
* `any_table()`: Percentages based on single formatted variable expressions can now be computed with the `pct_value` parameter. (05.03.2026)
* `any_table()`: If the `pct_value` or `pct_group` parameters are used but are not part of the statistics parameter, they will be automatically added to statistics. (05.03.2026)
* `dummy_data()`: Reworked the dummy data generation in mutliple ways:
	* Added new variables: NUTS2, NUTS3, number_of_persons, body_height, body_weight, income_class, expenses, balance. (09.03.2026)
	* Now generates up to five years. (09.03.2026)
	* Variables are now sorted by year, state, household_id, person_id. (09.03.2026)
	* household_id is now numbered individually within each state. (09.03.2026)
	* Weights now adapt to the number of observations so that weighted results stay roughly the same. (09.03.2026)
	* Brought variety in most of the variables so that different distributions aren't evenly spread anymore. (09.03.2026)
	* Adding NA values is now optional. (09.03.2026)

### Changed functionality

* `frequencies()`, `crosstabs()`, `any_table()`, `content_report()`: Now uses round half up rule for rounding values. (20.02.2026)

### Fixed

* `any_table()`: When there was an NA value in the first table column while the row label column was deleted, the NA symbol wasn't set. This is fixed now. (12.02.2026)
* `any_table()`: If the custom NA symbol is set to a number, Excels number stored as text error is now ignored. (12.02.2026)
* `transpose_plus()`: Took out a debug print I forgot in the function. (12.02.2026)
* `summarise_plus()`: Fixed formats not matching data when computing percentages, if numeric values are stored as character. (18.02.2026)
* `if.()`, `else_if.()`, `else.()`: When passing a vector to a new variable, the functions don't error any more if there are NA values. (22.02.2026)
* `summarise_plus()`: Fixed "." in a variable expression is now preserved as intended. (09.03.2026)
* `frequencies()`: When by variables were used the first by expression was omitted. This is fixed now. (09.03.2026)

### Optimization

* `dummy_data()`: Got rid of the loop for generating multiple years at random. Now the dummy data is based on a smaller set of observations, which allows faster variable generation. Years are only added as cartesian product at the end and values altered per year. This allowed the rework mentioned above: More variables and more variety in the same amount of time as before. (09.03.2026)

### New Error Checks

* `any_table()`: Added an error check in case all calculations are done and no value variable was computed. (05.03.2026)

### Additionally

* `frequencies()`: Fixed two of the examples where `list` was missing in formats parameter. (14.02.2026)

# qol 1.2.1

CRAN release on 11.02.2026

### New functions

* `expand_formats()`: Generates a data frame which contains all nested combinations of the provided format labels. (15.01.2026)
* `set_print_miss()`, `get_print_miss()`: Additional global setters and getters. (15.01.2026)
* `concat()`: Concatenate multiple variables inside a data frame into a new variable with automatic or individual padding. (21.01.2026)
* `sub_string()`: Can extract text from left, right or middle and is able to look up characters as start or end position. (22.01.2026)
* `remove_blanks()`: Removes blanks in the expressions of a character variable. (24.01.2026)
* `macro()`, `apply_macro()`: Resolve objects starting with "&" within a text. Functions which can print titles, footnotes, variable or statistic labels make use of these functions, so that e.g. a title can be passed as "The current year is &year" and will be resolved to "The current year is 2026". (24.01.2026)
* `where.()`: A quick way to filter a data frame and get a direct view of the result. (27.01.2026)
* `free_memory()`: Provides more flexible ways to remove objects from memory. (28.01.2026)
* `import_multi()`: Import multiple csv or xlsx files. The function is also capable of importing all sheets from multiple xlsx file. (10.02.2026)
* `export_multi()`: Export multiple csv or xlsx files based on a list of data frames. The function is also capable of exporting all data frames to multiple sheets in one xlsx file. (10.02.2026)
* `get_duplicate_var_names()`: Checks for duplicate variable names in a data frame, e.g. AGE, age and Age. (11.02.2026)
* `get_duplicate_var_count()`: Counts the number of duplicated variables in a data frame. If a variable appears three times, e.g. AGE, age and Age, the variable count will be one. (11.02.2026)

### New functionality

* `summarise_plus()`, `any_table()`, `frequencies()`, `crosstabs()`: The new parameter `print_miss` outputs all possible categories of the grouping variables based on the provided formats, even if there are no observations for a combination. (15.01.2026)
* `retain_variables()`: The ":" can now be used as a placeholder for "starts with" ("text:"), "ends with" (":text") and "contains" (":text:"). (15.01.2026)
* `excel_output_style()`: New `subheader` parameters which come into play when setting the `by_as_subheaders` to TRUE when using `any_table()`. The parameters can also be set as global option. (18.01.2026)
* `any_table()`: When using by variables with the new styling option `by_as_subheaders` the tables aren't split among multiple sheets, instead the by variable expressions are used as subheaders in one big table. (18.01.2026)
* `summarise_plus()`, `any_table()`, `crosstabs()`: `statistics` parameter can now be passed without quotation marks. (11.02.2026)
* `any_table()`: `pct_group` parameter can now be passed without quotation marks. (11.02.2026)

### Changed functionality

* `frequencies()`: Until now the function always printed a means and a frequencies table as default. Now it only prints a frequencies table as default to get the main results as fast as possible on screen. The means table can be activated again with the new `means` parameter. (17.01.2026)
* `frequencies()`: When using multiple by variables with excel `output`, worksheets are now ordered in provided variable order instead of alternating. (17.01.2026)
* `mark_case()`: Now outputs a variable with 1/0 instead of TRUE/FALSE. (18.01.2026)
* `recode.()`: Removed `new_var` parameter. Recode now doesn't return the whole data frame anymore, but only the recoded variable as a vector. So instead of writing `my_data <- my_data |> recode.("age_group", age = age.)`, the syntax is now more natural like this `my_data[["age_group"]] <- my_data |> recode.(age = age.)`. (21.01.2026)
* `running_number()`, `mark_case()`, `retain_value()`, `retain_sum()`: Removed `var_name` parameter. Functions now don't return the whole data frame anymore, but only the retained variable as a vector or list of vectors. So instead of writing e.g. `my_data <- my_data |> running_number("running")`, the syntax is now more natural like this `my_data[["running"]] <- my_data |> running_number()`. (22.01.2026)
* `any_table()`: When using pre summarised data, the error handling is now less restrict. Instead of aborting if the TYPE variable is missing, it is now auto generated. Additionally if statistic extensions are missing to the value variable names, the function now doesn't abort, instead extensions are now added according to the provided statistics and a warning is thrown. (23.01.2026)
* `summarise_plus()`: Numeric values stored as characters are now returned as character, while originally numeric variables stay numeric. (24.01.2026)
* `any_table()`: Instead of aborting when no values are passed, the function now generates a variable to output unweighted results. (02.02.2026)

### Fixed

* `frequencies()`: Mean tables are now printed when by variables are provided. (17.01.2026)
* `frequencies()`: No empty "total" table is printed when by variables are provided. (17.01.2026)
* `frequencies()`: "total" row is not printed anymore, when variables use multilabels and are computed with a by group. (17.01.2026)
* Unit test: Fixed global footnote option unit test. `set_titles()` was called instead of `set_footnotes()`. (18.01.2026)
* `any_table()`: When variable names started with the same base name and variable labels should be assigned, it could happen that the label of the shortest variable was applied to all variables. Now variable labels are always assigned from longest to shortest variable name to prevent this. (23.01.2026)
* `any_table()`: If a variable name was part of another variable label, it could happen that the already set label was altered with the label of the variable appearing in the label. This can't happen anymore. (24.01.2026)
* `summarise_plus()`: When a factor variable is used as class variable and a factor level has a "." in it, it is now output as provided instead of the "." being converted to "!!!". (24.01.2026)
* `summarise_plus()`: When a character variable is used as class variable and there is a "." in an expression, the function doesn't error anymore. (24.01.2026)
* `any_table()`: When removing the value variable label with "" from the column header, so that the header line is removed, the alternation of the header lines won't mess up anymore. (28.01.2026)
* `any_table()`: The function ran into an error when statistic labels where set in the variable labels parameter. This is no more possible. (28.01.2026)
* `build_master()`: Added `library(qol)` to the top of the file. Loading the package is neccessary so that the `libname()` functions are able to run. (29.01.2026)
* `any_table()`: Percentages could have the wrong number of decimal places, if the group percentage variable name ended in a specific way and a number. (05.02.2026)

### Optimization

* `summarise_plus()`: When only statistics based on sums are selected, the function already pre summarises the data frame, to apply the formats on a much smaller data frame. When using nesting = "all"/"single" the data frame is now pre summarised a second time before applying formats, this time only using the grouping variables of the combination beeing processed. This drastically cuts down memory allocation - especially for larger data frames - and speeds up every iteration significantly. In addition to this function `any_table()` benefits greatly from this optimization. (16.01.2026)
* `any_table()`, `frequencies()`, `crosstabs()`: When using by variables in excel `output`, the new `print_miss` option enables a shortcut in formatting the sheets after the first one. Since the option guarantees that all follow up sheets are printed with the exact same table width and height, because all categories are printed, only the first sheet must be formatted. All other sheets can clone the entire style from the first sheet. (17.01.2026)
* `replace_except()`: Got rid of the nested for loop. This saves time in `any_table()` on larger data frames. (17.01.2026)
* `reorder_combination()`: Reordering is now only done on unique vector values instead of a whole larger vector. This saves time in `any_table()` on larger data frames. (17.01.2026)

### New Error Checks

* `combine_into_workbook()`: Added an error check in case a provided object is no `any_table()` result list. (17.01.2026)
* `discrete_format()`, `interval_format()`: Added an error check in case something other than list elements are provided. (29.01.2026)

### Additionally

* `any_table()`: Removed c() in examples where not necessary. (24.01.2026)
* `if.()`: Now outputs a message on how many observations have been removed and how many are left. (27.01.2026)
* `any_table()`, `combine_into_workbook()`: Adjusted examples. (02.02.2026)
* `if.()`, `else_if.()`, `else.()`: If used inside a function, these functions should now be able to catch the original variable name passed into the parent function. (10.02.2026)
* `content_report`: Added duplicate variable count to global information. (11.02.2026)


# qol 1.2.0

CRAN release on 13.01.2026

### New functions

* `vars_between()`: Get variable names between two variables in a data frame. (21.12.2025)
* `convert_factor()`: Converts all given variables to factor. (23.12.2025)
* `transpose_plus()`: Format driven transposition, which is able to do more than just transpose values. (27.12.2025)
* `sort_plus()`: Sort data frame observations with some additions. (27.12.2025)
* `split_by()`: Since `split_by_var()` didn't have any benefit, `split_by_var()` and `split_by_condition()` are now fused into one function. Additionally to give the new function more power, formats can be used to not only split up a data frame by the actual values in the data frame, but also into the desired ones passed with a format. Using multilabels it is also possible to generate completely new values and therefore data frames on the fly. (30.12.2025)
* `set()`: Stack data frames by column names with optional character compression. (30.12.2025)
* Error handling functions:
	* `resolve_intersection()`: Compares if two vectors have intersecting values. (02.01.2026)
	* `part_of_df()`: Check if variable names are part of a data frame. (02.01.2026)
	* `remove_doubled_values()`: Remove values from a vector that appear more than once. (02.01.2026)
	* `check_weight()`: Check whether a suitable weight variable was provided. (02.01.2026)
* `rename_multi()`: Rename one or more variables. (02.01.2026)
* `retain_variables()`: Order variables to the front or back of a data frame. Can also add empty variables. (04.01.2026)
* `add_variable_range()`: Add empty variables to a data frame in the provided range. (04.01.2026)
* Global style options:
	* `set_style_options()`: Sets the options from `excel_output_style()` and `number_format_style()` to a global environment so that every function which is capable of outputting styled Excel workbooks can use them without passing the `style` parameter every time individually. (06.01.2026)
	* `get_style_options()`: Print the currently set global options. (06.01.2026)
	* `reset_style_options()`: Set global style options to default values of `excel_output_style()` and `number_format_style()`. (06.01.2026)
	* `close_file()`: A simple, more readable wrapper for setting file parameter to NULL. (07.01.2026)
	* `set_variable_labels()`, `get_variable_labels()`, `set_stat_labels()`, `get_stat_labels()`, `set_print()`, `get_print()`, `set_monitor()`, `get_monitor()`, `set_na.rm()`, `get_na.rm()`: Additional global setters and getters. (07.01.2026)
	* `set_output()`, `get_output()`: Additional global setters and getters. (11.01.2026)
	* `set_titles()`, `get_titles()`, `set_footnotes()`, `get_footnotes()`: Additional global setters and getters. (11.01.2026)
* `content_report()`: Collects and prints global and per variable information about the provided data frame. (08.01.2026)
* `import_data()`, `export_data()`: Lightweight import and export for csv and xlsx files. (10.01.2026)
* `first_row_as_names()`: Sets the values of the first data frame row as variable names and deletes first row. (10.01.2026)
* `qol_news()`: Opens changelog GitHub Page. (11.01.2026)

### New functionality

* `inverse()`: Now supports variable names written without quotation marks. (21.12.2025)
* `keep()`/`dropp()`: Now support variable ranges, like "state:income". (21.12.2025)
* `any_table()`: Removed `pre_summed` parameter. Instead the function now checks on it's own, whether the provided data frame is pre summarised or not. (23.12.2025)
* `dots_to_char()`: Renamed from `args_to_char()` and is now able to get the original argument from up the environment tree and return it as character vector. (02.01.2026)
* `args_to_char()`: Now converts the contents of a given argument to a character vector. (02.01.2026)
* `excel_output_style()`: Parameter `file` is now split up into `save_path` and `file` (meaning just the file name + extension). (06.01.2026)
* `any_table()`, `frequencies()`, `crosstabs()`, `export_with_style()`: The `style` parameter is now set to the new global styling options as default instead of `excel_output_style()`. (06.01.2026)
* `any_table()`: The `var_labels` and `stat_labels` parameter is now set to the new global options as default. (07.01.2026)
* In general: New global options for `print`, `monitor` and `na.rm` have been implemented into the functions capable of using them. (07.01.2026)
* `if.()`: If only a single variable name is provided, it is now evaluated as !is.na(var_name). (10.01.2026)
* `retain_value()`: Reworked iternally, can now also handle character variables. (10.01.2026)
* `excel_output_style()`: New parameters `as_heatmap`, `heatmap_low_color`, `heatmap_middle_color` and `heatmap_high_color`. These can also be set as global options. If `as_heatmap` is TRUE the tables from `any_table()`, `frequencies()`, `crosstabs()` and `export_with_style()` receive a conditional formatting. (11.01.2026)
* `crosstabs()`: Now has a new parameter `show_total` to control output of row and column totals. (11.01.2026)
* `keep()`/`dropp()`: The ":" can now be used as a placeholder for "starts with" ("text:"), "ends with" (":text") and "contains" (":text:"). (12.01.2026)
* `any_table()`: Is now able to generate 'pct_value' statistic without outputting additional 'sum' values. (12.01.2026)
* `build_master()`: Now generates a code block which can rebuilt the master file. Additionally the folder structure is now generated in code blocks which can open the folders and files. Overall gave the master file a bit more visual structure. (12.01.2026)

### Removed

* `split_by_var()` and `split_by_condition()`: See comment under 'New functions'. (30.12.2025)
* `is_numeric()`: There was simply no benefit. (02.01.2026)

### Fixed

* `keep()`: Variables where always output in provided order. order_vars = FALSE (default) will now output variables in order of appearance. (21.12.2025)
* `any_table()`: Now checks if a column combination is also part of the row combinations. (22.12.2025)
* `any_table()`: Fixed row header variables where sorted alphabetically instead of in provided order. Bug was introduced in version 1.1.1. (23.12.2025)
* `any_table()`: Fixed by variables could be sorted in the wrong order. (30.12.2025)
* `any_table()`: Added missing format for variable 'state' in examples. (02.01.2026)
* `frequencies()`: 'formats' parameter was missing a '=' in examples. (02.01.2026)
* `summarise_plus()`: Group percentages with nesting = "all" or "single" and na.rm = TRUE are now computed as intended. (02.01.2026)
* `handle_cell_styles()`: Set apply_font and font_id in a save way to prevent warnings. (04.01.2026, thanks to @JanMarvin)
* `summarise_plus()`: Removed conversion to numeric values before applying formats, which could lead to not matching formats, if a numeric value was intentionally stored as character value. (07.01.2026)
* `any_table()`: When na.rm was TRUE and many table cells where generated while only having few observations, it could happen that some combinations weren't generated and a result mismatch happened. Results are now joined instead of cbind together to be safe. (11.01.2026)

### Optimization

* `summarise_plus()`: Now uses faster `collapse::na_omit` for NA removal. (27.12.2025)
* `dummy_data()`: Optimized and now just takes half the time to generate data. (27.12.2025)
* `summarise_plus()`: Swapped in more `collapse` functions. Turned off sorting of `collapse::GRP` when using the shortcut route. (28.12.2025)
* `apply_formats()`: NA value subsetting is now only done once and not twice with interval formats. `data.table::setkey` is now only called on the format data frame. (28.12.2025)
* `summarise_plus()`: When na.rm option is TRUE and only statistics based on sums are selected, `summarise_plus()` is now able to take the shortcut route. This also gives `any_table()` a huge performance boost when na.rm option is TRUE. (29.12.2025)
* In general: Swapped in more `collapse` functions. (29.12.2025)
* `any_table()`: If more than two group percentages have to be computed, any additional one gets computed faster, because they are computed on a smaller data frame. (29.12.2025)
* In general: Error handling has been generalized in many places to make it more robust and get a better overview. (02.01.2026)
* `apply_format()`: Removed redundant data.table transformations. (11.01.2026)

### New Error Checks

* `any_table()`: Added an error check in case a variable combination was provided, which is not part of a pre summarised data frame. (23.12.2025)
* `summarise_plus()` and `any_table()`: Added an error check in case an invalid statistic is provided. (29.12.2025)
* `summarise_plus()` and `any_table()`: Added an error check in case an invalid type is provided. (02.01.2026)
* `recode_multi()`: Added an error check in case an unknown object is provided. (02.01.2026)
* `any_table()`, `frequencies()`, `crosstabs()`, `export_with_style()`: Added check if specified save path exists. (08.01.2026)
* `export_with_style()`: Added check for output. (11.01.2026)

### Additionally

* Restructured some "Small Helpers" into "Renaming" and "Variable Selection". (21.12.2025)
* Adjusted some warning messages. (22.12.2025)
* Added some comments to the heavier functions, to enhance visual code structure. (22.12.2025)
* Added `transpose_plus()` and `sort_plus()` examples to the README. (27.12.2025)
* `discrete_format()` and `interval_format()`: Labels will now be converted to numeric if they are all numeric. (02.01.2026)
* In general: Added more messages to display what functions do. (03.01.2026)
* `retain_value()`, `retain_sum()`: `value` parameter is now called `values`. (10.01.2026)
* `mark_case()`, `retain_value()`, `retain_sum()`: Adjusted unit tests to something that actually makes sense. (10.01.2026)
* In general: Added some unit tests on file saving and retrieving. (10.01.2026)
* `any_table()`, `frequencies()`, `crosstabs()`, `export_with_style()`: Updated examples to show how a file is saved via the style element. (13.01.2026)

### GitHub related

* Updated article on further comparison with SAS on import csv and xlsx and `content_report()` as well as the new `if.()` selection with only providing a variable name. Also added `transpose_plus()` example and selecting ranges with `keep()`/`dropp()`. (12.01.2026)


# qol 1.1.1

CRAN release on 13.12.2025

### New functions

* `multi_join()`: Join two or more data frames together in one operation. (30.11.2025)
* `libname()`: Check if path exists and retrieve files. (04.12.2025)

### Fixed

* `any_table()`: Fixed multi layered column header labels where not applied correct. (28.11.2025)
* `any_table()`: Fixed incorrect column order when using order_by "values" while variable names have underscores. (04.12.2025)
* `any_table()`: Using pre summed data now also works, if variable names carry underscores. (04.12.2025)

### GitHub related

* Added article comparing this package with SAS even further. (02.12.2025)


# qol 1.1.0

CRAN release on 20.11.2025

### New functions

* `build_master()`: Reads a given folder structure, which contains scripts, and builds a master script as a markdown file. (18.10.2025)
* `build_rstheme()`: Build a complete theme file, which can be used to change the visual appearance of RStudio. (23.10.2025)
* `combine_into_workbook()`: Combines any number of tables created with `any_table()` into one workbook and styles them according to their meta information. (26.10.2025)
* `replace_except()`: Replaces a provided pattern with another, while protecting exceptions. (29.10.2025)
* `mark_case()`: Marks first or last cases within a provided group. (31.10.2025)
* `retain_value()`: Retains the first value for all cases of the same group. (31.10.2025)
* `retain_sum()`: Retains the summarised values for all cases of the same group. (31.10.2025)

### New functionality

* `interval_format()`: Implemented keywords "low "and "high" with which one can define pseudo low or high values, if one doesn't know, what the minimum or maximum value of a variable is. (20.10.2025)
* `discrete_format()`: Implemented keyword "other" with which one can catch any other value not covered by the explicitly specified values. (27.10.2025)
* `any_table()`: Now returns styling meta information as a third list element. This meta information can be used by `combine_into_workbook()`. (26.10.2025)
* `any_table()`: Now supports underscores in variable names. (29.10.2025)
* `any_table()`: Added new column ordering possibilities by "columns" or "values_stats". (03.11.2025)
* `any_table()`: Can now output tables even though no column variables are specified. (05.11.2025)

### Fixed

* In `any_table()` the header and table row heights as well as the column widths set by the style option where 1 row/column to short. (14.10.2025)
* In `any_table()` the row heights didn't catch the whole table. (14.10.2025)
* `any_table()` ran into an error, if a variable was provided as pct_group, which was not part of the row and column variables. (14.10.2025)
* Fixed typos in frequencies examples, where it said "frequency" instead of "frequencies". (18.10.2025)
* In Excel outputs the number stored as text error is now ignored. (19.10.2025, thanks to @JanMarvin)
* Fixed table length captured with too many rows. (19.10.2025)
* `any_table()`: Fixed order_by stats not working as intended in some cases. (03.11.2025)
* `any_table()`: Fixed table rows not ordered correctly in some cases. (03.11.2025)
* `any_table()`: Depending on variable constellation and ordering of the column header it could happen, that the header wasn't merged correct in the Excel workbook. This was fixed. (04.11.2025)
* `any_table()`: Doesn't run into an error, if the table only consists of one value column. (05.11.2025)

### Changed functionality

* In `excel_output_style()` the options `column_widths` and `row_heights` now start at the first column/row instead of the beginning of the table. (19.10.2025)
* In `summarise_plus()`, if summarised values should be merged back, the variables TYPE, TYPE_NR and DEPTHS are now not merged back anymore. (31.10.2025)

### Additionally

* Added missing functions `export_with_style()` and `get_excel_range()` to the ?qol overview page. (14.10.2025)
* Added information to the startup message to use ?qol to get an overview. (14.10.2025)
* Now using openxlsx2 helper to convert row and column numbers to Excel ranges. (19.10.2025, thanks to @JanMarvin)
* All Excel tables now have named ranges for the table and the values. (19.10.2025, thanks to @JanMarvin)
* Added an example to the README showing how to save an Excel workbook to the filesystem. (19.10.2025)
* Used lintr package for some code cleanup. (20.10.2025, thanks to @JanMarvin for the advise)
* Added custom theme section to the README. (23.10.2025)
* Added message, if a format is applied to a factor variable. (27.10.2025)
* Excel workbooks will now only be opened in interactive sessions. (29.10.2025, thanks to @JanMarvin)
* Corrected typos in snippets. (03.11.2025)

### GitHub related

* Added article comparing this package with SAS. (14.10.2025)
* Changed GitHub Page style. (14.10.2025)
* Added example themes with corresponding code. (23.10.2025)


# qol 1.0.2

CRAN release on 14.10.2025

### DESCRIPTION file

* Fixed brackets in DESCRIPTION so that auto linking works.
* Added another URL.

### Fixed

* There could be an error in `summarise_plus()`, `any_table()` and `args_to_char()`, if there where to many variables provided.
* `summarise_plus()` ran into an error, if a value label from a format contained a ".".
* `any_table()` could lead to wrong results if the pre_summed option was used and a variable was part of multiple combinations in the summarised data.

### Changed functionality

* In `summarise_plus()`, when types are defined, the total row is now removed if not explicitly defined as type.
* In `summarise_plus()`, when the nesting option "deepest" is used, the variables TYPE, TYPE_NR and DEPTHS are now also generated.

### Unit tests

* Adjusted tests according to changed functionalities

### Additionally

* Added openxlsx2 as import in the qol main help file
* Corrected a typo in a warning message in `any_table()` concerning variable order.


# qol 1.0.1

CRAN release on 10.10.2025

* Added references to specific SAS functions in the description field of the Description file where they are mentioned.
* Removed specific seed in R/dummy_data.R
* Fixed a bug in dummy_data where it could happen that not enough observations where generated.


# qol 1.0.0 

* Initial CRAN submission.
