Open-source agentic AI for transaction monitoring. Just ask Jooby.What's Jube? →

Loading, Shaping and Merging Data

The original Module 4 covered horizontal abstraction with numeric and string functions, regular expressions, dates, importing CSV and pipe-separated files, connecting to and fetching from SQL Server, then sorting, selecting, mutating, merging and deleting columns of a data frame with dplyr, finishing with a CSV export. Every procedure below is condensed, re-run for real, and followed by its AdvancedAnalytics equivalent where one exists.

One point applies to the whole module and is worth stating up front: loading data in the product does not route through R at all. There is no analogue anywhere in AdvancedAnalyticsTools for “open a file” or “connect to a database” — a caller already holds a table (rows of doubles from the platform’s own Archive/model payload data) before any tool in DataPreparationAndResampling runs, and hands it in as double[][], which the service packs into AdvancedAnalyticsCache via AdvancedAnalyticsPacking.Pack (Rows/Columns shape, IEEE 754 binary64, little endian, row major — see docs/AnalyticalMethodology/Reference/FoundationData.md). Every tool cited below operates on that already-packed array, never on a CSV, a pipe-delimited file or a live database connection.

Procedure 1: Using Numeric Functions to Create a Horizontal Abstraction

R’s base numeric operators (+ - * / %% ^) and functions such as round() and sqrt() can be nested to build a derived column from a raw one:

Double <- c(1.22341,5.889988,6.9999890)
Double

DoubleRound <- round(Double,2)
DoubleRound

DoubleRoundSqrt <- sqrt(DoubleRound)
DoubleRoundSqrt

DoubleNested <- sqrt(round(Double,2))
DoubleNested

Console output:

[1] 1.223410 5.889988 6.999989
[1] 1.22 5.89 7.00
[1] 1.104536 2.426932 2.645751
[1] 1.104536 2.426932 2.645751

Tool equivalent: Jube.Service.Agent.AdvancedAnalyticsTools.DataPreparationAndResampling.ScalingTools / tool "ApplyColumnFunction" covers the sqrt() step — it applies one fixed unary function (natural log, log10, log2, log1p, signed log1p, square root, signed square root, cube root, reciprocal, square or absolute value) to every cell of a table, with an optional additive offset before the function runs. Rounding to a fixed number of decimal places has no tool equivalent: values stay full double precision throughout the packed array, and there is no “round to N decimals” step anywhere in the tool layer — rounding is presentation, not a shaping operation the product performs.

Procedure 2: Extracting a Substring, Testing Logically and Presenting for Machine Learning

substr() pulls a fixed-position slice out of a character vector; comparing it and converting the logical result with as.numeric() turns a text pattern into a model-ready flag:

Names <- c("Richard","Robert","Reinhard","Raymond","Richardino","Richardo")
Names

NamesSubstr <- substr(Names,1,7)
NamesSubstr

NamesSubstrLogical <- NamesSubstr == "Richard"
NamesSubstrLogical

NamesSubstrLogicalNumeric <- as.numeric(NamesSubstrLogical)
NamesSubstrLogicalNumeric

NamesSubstrLogicalNumericNested <- as.numeric(substr(Names,1,7) == "Richard")
NamesSubstrLogicalNumericNested

Console output:

[1] "Richard"    "Robert"     "Reinhard"   "Raymond"    "Richardino"
[6] "Richardo"  
[1] "Richard" "Robert"  "Reinhar" "Raymond" "Richard" "Richard"
[1]  TRUE FALSE FALSE FALSE  TRUE  TRUE
[1] 1 0 0 0 1 1
[1] 1 0 0 0 1 1

Tool equivalent: none — R/script-runner only. AdvancedAnalyticsCache holds nothing but packed doubles; no tool in the semantic layer accepts or inspects raw strings, so there is no genuine home for “slice a substring and test it.” A flag like this has to already be a number by the time it reaches AdvancedAnalyticsTools — computed upstream, outside this service, before the table is ever packed.

Procedure 3: Searching with Regular Expressions

grepl() replaces the fixed-position substr() test with a pattern match, here anchored to the start of the string with ^:

Names <- c("Richard","Robert","Reinhard","Raymond","Richardino","Richardo")

NamesGrepl <- grepl("^Richard",Names)
NamesGrepl

NamesGreplNumeric <- as.numeric(NamesGrepl)
NamesGreplNumeric

NamesGreplNumericNested <- as.numeric(grepl("^Richard",Names))
NamesGreplNumericNested

Console output:

[1]  TRUE FALSE FALSE FALSE  TRUE  TRUE
[1] 1 0 0 0 1 1
[1] 1 0 0 0 1 1

Tool equivalent: none — R/script-runner only, for the same reason as Procedure 2: regular-expression matching is string work, and the packed array the tool layer operates on is numeric-only.

Procedure 4: Create a Date with a Specific Date and Time Format

A character vector of dates is converted to R’s native Date type with as.Date(), given a format string that names where the day, month and year tokens fall:

DatesString <- c("22/02/1732","30/10/1735","13/04/1743","16/03/1751")
DatesString
class(DatesString)

Dates <- as.Date(DatesString,format="%d/%m/%Y")
Dates
class(Dates)

Console output:

[1] "22/02/1732" "30/10/1735" "13/04/1743" "16/03/1751"
[1] "character"
[1] "1732-02-22" "1735-10-30" "1743-04-13" "1751-03-16"
[1] "Date"

Tool equivalent: none directly — the product has no Date object at all. Dates reach AdvancedAnalyticsTools already reduced to a number: either an ISO 8601 string or a Unix epoch value. Jube.Service.Agent.AdvancedAnalyticsTools.DataPreparationAndResampling.FeatureEngineeringTools / tools "ExtractDateTimeParts" (ISO 8601 strings) and "ExtractEpochParts" (epoch seconds or milliseconds) are the moment a date-like input actually becomes usable numeric feature columns — the nearest thing the tool layer has to “create a date,” except that the output is year/month/day/etc. parts rather than a Date type, because there is nothing downstream that would consume a Date type.

Procedure 5: Perform Date Arithmetic

Once a vector is a Date, ordinary +/- shifts every element by that many days:

DatesString <- c("22/02/1732","30/10/1735","13/04/1743","16/03/1751")
Dates <- as.Date(DatesString,format="%d/%m/%Y")

DatesPlusOne <- Dates + 1
DatesPlusOne

Console output:

[1] "1732-02-23" "1735-10-31" "1743-04-14" "1751-03-17"

Tool equivalent: FeatureEngineeringTools / tool "ComputeElapsedTime" is the realistic equivalent for date arithmetic in the product — it takes two parallel lists of ISO 8601 date-time strings and returns (to - from) in seconds, minutes, hours or days per row, which is how the product expresses account age or time-since-last-event. It answers “how far apart are these two moments,” not “shift this moment by a constant” — the toy Dates + 1 example itself (offsetting a date by a fixed amount rather than measuring an elapsed distance) has no direct tool.

Procedure 6: Extract Reporting Periods from a Date

weekdays(), months() and quarters() each pull one calendar field out of a Date vector:

DatesString <- c("22/02/1732","30/10/1735","13/04/1743","16/03/1751")
Dates <- as.Date(DatesString,format="%d/%m/%Y")

ReportingQuarters <- quarters(Dates)
ReportingQuarters

ReportingMonths <- months(Dates)
ReportingMonths

ReportingWeekdays <- weekdays(Dates)
ReportingWeekdays

Console output:

[1] "Q1" "Q4" "Q2" "Q1"
[1] "February" "October"  "April"    "March"   
[1] "Friday"   "Sunday"   "Saturday" "Tuesday"

Tool equivalent: FeatureEngineeringTools / tools "ExtractDateTimeParts" and "ExtractEpochParts" again — both return quarter, month and dayOfWeek (Monday 1 to Sunday 7, numeric rather than a name) among their fourteen parts (year, quarter, month, day, dayOfWeek, dayOfYear, isoWeek, hour, minute, second, secondsOfDay, isWeekend, isMonthStart, isMonthEnd), selectable individually through the parts argument. This is a direct, better-than-like-for-like match: one call returns every reporting period R needed three separate functions for, already as numbers instead of text a model cannot use.

Procedure 7: Importing a CSV File with R Studio

RStudio’s Import Dataset wizard is, under the hood, a code generator around readr::read_csv(). Reading the equity price file directly (the wizard step itself is a GUI convenience, not separate mechanics):

library(readr)

AAPL <- read_csv("/work/AAPL.csv")
AAPL
str(AAPL)

Console output:

Rows: 6 Columns: 3
── Column specification ────────────────────────────────────────────────────────
Delimiter: ","
chr  (1): Symbol
dbl  (1): Interim_Close
date (1): Interim_Buffer_Date

# A tibble: 6 × 3
  Symbol Interim_Buffer_Date Interim_Close
  <chr>  <date>                      <dbl>
1 AAPL   2024-01-02                   186.
2 AAPL   2024-01-03                   184.
3 AAPL   2024-01-04                   182.
4 AAPL   2024-01-05                   181.
5 AAPL   2024-01-08                   186.
6 AAPL   2024-01-09                   185.
spc_tbl_ [6 × 3] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ Symbol             : chr [1:6] "AAPL" "AAPL" "AAPL" "AAPL" ...
 $ Interim_Buffer_Date: Date[1:6], format: "2024-01-02" "2024-01-03" ...
 $ Interim_Close      : num [1:6] 186 184 182 181 186 ...

(Package installed beyond the shared set: readr 2.x, with its dependencies tzdb, progress and vroom, into the shared library — not part of the base image.)

Tool equivalent: none — as stated at the top of this module, there is no file-import step in the product’s analytics path at all. A table reaches AdvancedAnalyticsTools as a double[][] argument the caller already assembled from the platform’s own data (Archive/model payload records), never by opening a CSV.

Procedure 8: Importing a Pipe-Separated File

readr::read_delim() takes an explicit delimiter argument, so the same data with | instead of , reads identically apart from that one argument:

library(readr)

AAPL <- read_delim("/work/AAPL.txt","|")
AAPL
str(AAPL)

Console output:

Rows: 6 Columns: 3
── Column specification ────────────────────────────────────────────────────────
Delimiter: "|"
chr  (1): Symbol
dbl  (1): Interim_Close
date (1): Interim_Buffer_Date

# A tibble: 6 × 3
  Symbol Interim_Buffer_Date Interim_Close
  <chr>  <date>                      <dbl>
1 AAPL   2024-01-02                   186.
2 AAPL   2024-01-03                   184.
3 AAPL   2024-01-04                   182.
4 AAPL   2024-01-05                   181.
5 AAPL   2024-01-08                   186.
6 AAPL   2024-01-09                   185.
spc_tbl_ [6 × 3] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ Symbol             : chr [1:6] "AAPL" "AAPL" "AAPL" "AAPL" ...
 $ Interim_Buffer_Date: Date[1:6], format: "2024-01-02" "2024-01-03" ...
 $ Interim_Close      : num [1:6] 186 184 182 181 186 ...

Tool equivalent: none, for the same reason as Procedure 7 — the delimiter is irrelevant because there is no file-import path in the product to begin with.

Procedure 9: Connect to an SQL Server Database

Not executed against a live database. This procedure needs an external SQL Server instance ((local)\SQLEXPRESS, database Training) that the validation container does not have and cannot be given. The script below is kept as reference, exactly as the original guide wrote it, but its console output was never captured because it was never run here.

RODBC opens and holds a connection object for later queries:

library(RODBC)

Connection <- odbcDriverConnect("driver={SQL Server};server=(local)\\SQLEXPRESS;database=Training;username=sa;password=Training12345")

Tool equivalent: none. The product does not open ad hoc database connections from the analytics tool layer either — data already lives in the platform’s own Postgres tables (Archive/model payload data) and is assembled into a table by the caller before it ever reaches AdvancedAnalyticsTools. There is no tool anywhere in DataPreparationAndResampling (or elsewhere in the semantic layer) that takes a connection string.

Procedure 10: Fetch an Entire Table from an SQL Server Database

Not executed against a live database, for the same reason as Procedure 9 — kept as reference only.

AAPL <- sqlQuery(Connection,"select * from AAPL")
View(AAPL)

Tool equivalent: none, for the same reason as Procedure 9.

Procedure 11: Sorting a Data Frame with the arrange () Function

dplyr::arrange() reorders a data frame’s rows by one or more columns, ascending by default, desc() for descending, later columns breaking ties in earlier ones:

library(readr)
library(dplyr)

AAPL <- read_csv("/work/AAPL.csv")

AAPL <- arrange(AAPL,Interim_Buffer_Date)
AAPL

AAPL <- arrange(AAPL,desc(Interim_Buffer_Date))
AAPL

AAPL <- arrange(AAPL,desc(Interim_Buffer_Date),Interim_Close)
AAPL

Console output:

# A tibble: 6 × 3
  Symbol Interim_Buffer_Date Interim_Close
  <chr>  <date>                      <dbl>
1 AAPL   2024-01-02                   186.
2 AAPL   2024-01-03                   184.
3 AAPL   2024-01-04                   182.
4 AAPL   2024-01-05                   181.
5 AAPL   2024-01-08                   186.
6 AAPL   2024-01-09                   185.
# A tibble: 6 × 3
  Symbol Interim_Buffer_Date Interim_Close
  <chr>  <date>                      <dbl>
1 AAPL   2024-01-09                   185.
2 AAPL   2024-01-08                   186.
3 AAPL   2024-01-05                   181.
4 AAPL   2024-01-04                   182.
5 AAPL   2024-01-03                   184.
6 AAPL   2024-01-02                   186.
# A tibble: 6 × 3
  Symbol Interim_Buffer_Date Interim_Close
  <chr>  <date>                      <dbl>
1 AAPL   2024-01-09                   185.
2 AAPL   2024-01-08                   186.
3 AAPL   2024-01-05                   181.
4 AAPL   2024-01-04                   182.
5 AAPL   2024-01-03                   184.
6 AAPL   2024-01-02                   186.

Tool equivalent: Jube.Service.Agent.AdvancedAnalyticsTools.NumericsAndOptimisation.CombinatoricsAndUtilityTools / tools "SortValuesWithOriginalIndices" and "SortValuesByKeys" are the closest genuine primitives, but neither is a native multi-column table sort the way arrange() is. SortValuesByKeys reorders one array of values to follow the ascending sort order of a parallel key array (for example ordering amounts by their timestamps); SortValuesWithOriginalIndices stably sorts one array and hands back the original row positions so the same permutation can be applied to every other column by hand. Multi-column, mixed-direction sorting such as the third arrange() call above has no single-call equivalent — it would be composed from this primitive one key at a time.

Procedure 12: Specifying Columns of a Data Frame to Return

dplyr::select() keeps only the named columns, in the order given:

library(readr)
library(dplyr)

AAPL <- read_csv("/work/AAPL.csv")

AAPL <- select(AAPL,Symbol,Interim_Buffer_Date,Interim_Close)
AAPL

Console output:

# A tibble: 6 × 3
  Symbol Interim_Buffer_Date Interim_Close
  <chr>  <date>                      <dbl>
1 AAPL   2024-01-02                   186.
2 AAPL   2024-01-03                   184.
3 AAPL   2024-01-04                   182.
4 AAPL   2024-01-05                   181.
5 AAPL   2024-01-08                   186.
6 AAPL   2024-01-09                   185.

Tool equivalent: FeatureEngineeringTools / tool "SelectTableColumns" — returns a table containing only the named columns, in the order requested; an unknown name is rejected with the list of valid names. A direct, exact match for what select() does here.

Procedure 13: Adding Vectors to an Existing Data Frame

dplyr::mutate() adds a computed vector as a new column. Here, a 50% price-uplift target is built from Interim_Close:

library(readr)
library(dplyr)

AAPL <- read_csv("/work/AAPL.csv")

Target = AAPL$Interim_Close + (AAPL$Interim_Close / 2)
Target

AAPL <- mutate(AAPL,Target)
AAPL

Console output:

[1] 278.460 276.375 272.865 271.770 278.340 277.710
# A tibble: 6 × 4
  Symbol Interim_Buffer_Date Interim_Close Target
  <chr>  <date>                      <dbl>  <dbl>
1 AAPL   2024-01-02                   186.   278.
2 AAPL   2024-01-03                   184.   276.
3 AAPL   2024-01-04                   182.   273.
4 AAPL   2024-01-05                   181.   272.
5 AAPL   2024-01-08                   186.   278.
6 AAPL   2024-01-09                   185.   278.

Tool equivalent: there is no single generic mutate() — every derived column in FeatureEngineeringTools is its own explicit, typed tool rather than an arbitrary formula. The nearest family for this specific pattern (arithmetic between two numbers) is tool "GenerateColumnPairFeatures", which builds one new column per named pair of existing columns combined by product, ratio, sum, difference, absolute difference, minimum or maximum; tool "ApplyColumnFunction" (Procedure 1) covers a fixed nonlinear function of a single column; "GeneratePolynomialFeatures" covers powers and interaction terms. None of the three takes an arbitrary scalar multiplier the way Interim_Close / 2 does here — a business constant like that has no dedicated tool and is expected to already be baked into the column before it reaches the tool layer.

Procedure 14: Merging a Data Frame

dplyr::inner_join() brings two data frames together on a shared key, duplicating the right-hand row across every left-hand match:

library(readr)
library(dplyr)

AAPL <- read_csv("/work/AAPL.csv")
Descriptions <- read_csv("/work/Descriptions.csv")
Descriptions

AAPL <- inner_join(AAPL,Descriptions,by = "Symbol")
AAPL

Console output:

# A tibble: 1 × 2
  Symbol Description           
  <chr>  <chr>                 
1 AAPL   Apple Inc Common Stock
# A tibble: 6 × 4
  Symbol Interim_Buffer_Date Interim_Close Description           
  <chr>  <date>                      <dbl> <chr>                 
1 AAPL   2024-01-02                   186. Apple Inc Common Stock
2 AAPL   2024-01-03                   184. Apple Inc Common Stock
3 AAPL   2024-01-04                   182. Apple Inc Common Stock
4 AAPL   2024-01-05                   181. Apple Inc Common Stock
5 AAPL   2024-01-08                   186. Apple Inc Common Stock
6 AAPL   2024-01-09                   185. Apple Inc Common Stock

(The original guide’s script passed ID = "Symbol", which is not a valid argument of inner_join(); the correct argument is by, used above. It also names the source table EOD_Desccriptions, a typo for EOD_Descriptions, carried over unchanged since it is only prose here.)

Tool equivalent: none — R/script-runner only. No tool in DataPreparationAndResampling, or anywhere else in the semantic layer, joins two tables on a key column. A table reaching AdvancedAnalyticsTools is expected to already be one assembled, aligned set of rows; combining separate sources on a shared key happens upstream of the tool layer, not inside it.

Procedure 15: Delete a Vector from a Data Frame

Assigning NULL to a column removes it from a data frame:

library(readr)
library(dplyr)

AAPL <- read_csv("/work/AAPL.csv")
Target = AAPL$Interim_Close + (AAPL$Interim_Close / 2)
AAPL <- mutate(AAPL,Target)
AAPL

AAPL$Target <- NULL
AAPL

Console output:

# A tibble: 6 × 4
  Symbol Interim_Buffer_Date Interim_Close Target
  <chr>  <date>                      <dbl>  <dbl>
1 AAPL   2024-01-02                   186.   278.
2 AAPL   2024-01-03                   184.   276.
3 AAPL   2024-01-04                   182.   273.
4 AAPL   2024-01-05                   181.   272.
5 AAPL   2024-01-08                   186.   278.
6 AAPL   2024-01-09                   185.   278.
# A tibble: 6 × 3
  Symbol Interim_Buffer_Date Interim_Close
  <chr>  <date>                      <dbl>
1 AAPL   2024-01-02                   186.
2 AAPL   2024-01-03                   184.
3 AAPL   2024-01-04                   182.
4 AAPL   2024-01-05                   181.
5 AAPL   2024-01-08                   186.
6 AAPL   2024-01-09                   185.

Tool equivalent: FeatureEngineeringTools / tool "SelectTableColumns" again — there is no dedicated “drop this column” tool, but naming every column except the one to discard in the keep argument is exactly equivalent to AAPL$Target <- NULL. Where a column is dropped because it is mostly missing rather than by deliberate choice, Jube.Service.Agent.AdvancedAnalyticsTools.DataPreparationAndResampling.MissingValueTools / tool "DropSparseRowsAndColumns" is the data-quality-driven equivalent instead.

Procedure 16: Exporting a CSV File

write.csv() writes a data frame back out to the file system; getwd() reports where:

library(readr)

AAPL <- read_csv("/work/AAPL.csv")

write.csv(AAPL,file="/work/out/AAPL.csv")

getwd()

list.files("/work/out")
readLines("/work/out/AAPL.csv")

Console output:

[1] "/"
[1] "AAPL.csv"
[1] "\"\",\"Symbol\",\"Interim_Buffer_Date\",\"Interim_Close\""
[2] "\"1\",\"AAPL\",2024-01-02,185.64"                         
[3] "\"2\",\"AAPL\",2024-01-03,184.25"                         
[4] "\"3\",\"AAPL\",2024-01-04,181.91"                         
[5] "\"4\",\"AAPL\",2024-01-05,181.18"                         
[6] "\"5\",\"AAPL\",2024-01-08,185.56"                         
[7] "\"6\",\"AAPL\",2024-01-09,185.14"

(getwd() reports /, the validation container’s working directory rather than an RStudio project folder — the original guide’s screenshot showed a Windows desktop path; the mechanism demonstrated is identical.)

Tool equivalent: none — R/script-runner only, and deliberately the mirror image of Procedures 7 and 8: the product never writes a shaped table back out to a CSV file either. Results leave AdvancedAnalyticsTools as data returned to the caller (a FeatureTableResult, a trained-model record, a metric), which is then persisted through AdvancedAnalyticsTrainedModel/AdvancedAnalyticsTrainingRun and its child tables in Postgres, or serialised as JSON — never as a file on disk.