AbnormalReturns Example
As a quick example:
using CSV, DataFramesMeta, Dates, AbnormalReturns
df_firm = CSV.File(joinpath(data_dir, "daily_ret.csv")) |> DataFrame
df_mkt = CSV.File(joinpath(data_dir, "mkt_ret.csv")) |> DataFrame
df_mkt[!, :mkt] = df_mkt.mktrf .+ df_mkt.rf
df_events = CSV.File(joinpath(data_dir, "firm_earnings_announcements.csv")) |> DataFrame
mkt_data = MarketData(
df_mkt,
df_firm
)
df_events = @chain df_events begin
@rtransform(
:est_start = advancebdays(mkt_data.calendar, :ea, -120),
:est_end = advancebdays(mkt_data.calendar, :ea, -2),
:event_start = advancebdays(mkt_data.calendar, :ea, -1),
:event_end = advancebdays(mkt_data.calendar, :ea, 1),
)
@transform(:reg = quick_reg(mkt_data[:permno, :est_start .. :est_end], @formula(ret ~ mkt + smb + hml)))
@transform(
:bhar_reg = bhar(mkt_data[:permno, :event_start .. :event_end], :reg),
:bhar_simple = bhar(mkt_data[:permno, :event_start .. :event_end, ["ret", "mkt"]]),
:car_reg = car(mkt_data[:permno, :event_start .. :event_end], :reg),
:car_simple = car(mkt_data[:permno, :event_start .. :event_end, ["ret", "mkt"]]),
:total_ret = bh_return(mkt_data[:permno, :event_start .. :event_end, ["ret"]]),
:total_mkt_ret = bh_return(mkt_data[:permno, :event_start .. :event_end, ["mkt"]]),
)
@rtransform(
:std = std(:reg),
:var = var(:reg),
)
select(Not([:est_start, :est_end, :event_start, :event_end, :reg]))
# columns eliminated to save space:
select(Not([:car_reg, :car_simple, :var, :total_mkt_ret]))
end
283×6 DataFrame
Row │ permno ea bhar_reg bhar_simple total_ret std
│ Int64 Date Float64? Float64 Float64 Float64?
─────┼────────────────────────────────────────────────────────────────────────
1 │ 49373 2020-03-05 -0.0114753 -0.0368966 -0.0494716 0.0120205
2 │ 23660 2020-03-19 -0.0761877 -0.0799651 -0.16273 0.0100327
3 │ 17144 2020-03-18 0.00584866 -0.00749583 0.00562007 0.0122784
4 │ 52708 2020-03-19 0.0449673 0.057314 -0.0254504 0.023234
5 │ 57665 2020-03-24 0.105811 0.0931054 0.171386 0.0105352
6 │ 61621 2020-03-25 0.125142 0.129935 0.303036 0.0132682
7 │ 10104 2020-03-12 0.0295405 0.0515017 -0.01338 0.00903142
8 │ 75154 2020-03-19 0.15375 0.0269029 -0.0558615 0.0312084
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
277 │ 90373 2020-10-29 -0.00714584 -0.00486512 -0.0422143 0.016151
278 │ 90386 2020-11-02 -0.120063 -0.0769287 -0.0606557 0.0256068
279 │ 90993 2020-10-29 -0.00087777 0.00495886 -0.0323903 0.0109232
280 │ 90880 2020-10-28 0.00943539 -0.00174027 -0.0271723 0.0109558
281 │ 91668 2020-10-30 0.0290468 0.0200758 0.0283726 0.017888
282 │ 12449 2020-11-05 -0.0402344 -0.0572696 -0.0128859 0.0183221
283 │ 61399 2020-11-18 -0.0761987 -0.0706244 -0.0759727 0.0134081
268 rows omitted
Data
For the basic data, this uses the files in the test folder of this package ("test\data"). The "daily_ret.csv" file is a selection of firm returns, while "mkt_ret.csv" includes the average market return along with some Fama-French factor returns, you can download similar Fama-French data from here or from FamaFrenchData.jl and stock market data from AlphaVantage.jl or WRDSMerger.jl (requires access to the WRDS database).
The firm data uses "Permno" to identify a stock. This package will work with other identifiers, as long as the identifier-date pair is unique. However, Integers and Symbols will often be fastest (as opposed to String identifiers).
Load the firm data:
df_firm = CSV.File(joinpath(data_dir, "daily_ret.csv")) |> DataFrame
48659×4 DataFrame
Row │ permno date ret vol
│ Int64 Date Float64? Float64
───────┼──────────────────────────────────────────────────
1 │ 10104 2019-01-02 0.00155038 1.43204e7
2 │ 10104 2019-01-03 -0.00973026 1.98687e7
3 │ 10104 2019-01-04 0.0430996 2.0984e7
4 │ 10104 2019-01-07 0.0158425 1.79679e7
5 │ 10104 2019-01-08 0.00906218 1.62557e7
6 │ 10104 2019-01-09 -0.0020886 1.91002e7
7 │ 10104 2019-01-10 0.00083719 1.66527e7
8 │ 10104 2019-01-11 0.00982855 1.63975e7
⋮ │ ⋮ ⋮ ⋮ ⋮
48653 │ 91668 2020-12-22 0.0112328 332197.0
48654 │ 91668 2020-12-23 0.00639975 182199.0
48655 │ 91668 2020-12-24 0.00367913 39932.0
48656 │ 91668 2020-12-28 0.0184641 162785.0
48657 │ 91668 2020-12-29 -0.0155966 140853.0
48658 │ 91668 2020-12-30 0.0124131 119724.0
48659 │ 91668 2020-12-31 -0.00222926 196481.0
48644 rows omitted
and the market data:
df_mkt = CSV.File(joinpath(data_dir, "mkt_ret.csv")) |> DataFrame
672×6 DataFrame
Row │ date mktrf smb hml rf umd
│ Date Float64 Float64 Float64 Float64 Float64
─────┼─────────────────────────────────────────────────────────
1 │ 2019-01-02 0.0023 0.006 0.0113 0.0001 -0.023
2 │ 2019-01-03 -0.0245 0.0036 0.012 0.0001 -0.0078
3 │ 2019-01-04 0.0355 0.0041 -0.007 0.0001 -0.0097
4 │ 2019-01-07 0.0094 0.0101 -0.0074 0.0001 -0.0077
5 │ 2019-01-08 0.0101 0.0054 -0.0063 0.0001 0.0011
6 │ 2019-01-09 0.0056 0.0046 0.001 0.0001 -0.0083
7 │ 2019-01-10 0.0042 0.0003 -0.0046 0.0001 -0.0037
8 │ 2019-01-11 -0.0001 0.0012 0.0022 0.0001 -0.002
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
666 │ 2021-08-23 0.0108 0.0092 -0.0083 0.0 0.0076
667 │ 2021-08-24 0.0041 0.0034 0.0038 0.0 0.0094
668 │ 2021-08-25 0.0026 -0.0004 0.0034 0.0 0.0072
669 │ 2021-08-26 -0.0066 -0.004 -0.0031 0.0 -0.0031
670 │ 2021-08-27 0.0108 0.0162 0.003 0.0 0.0094
671 │ 2021-08-30 0.0035 -0.0035 -0.0138 0.0 -0.0117
672 │ 2021-08-31 -0.0013 0.0043 -0.0005 0.0 -0.002
657 rows omitted
Arranging and Accessing the Data
Next, load the data into a MarketData
object:
mkt_data = MarketData(
df_mkt,
df_firm;
id_col=:permno,# default
date_col_firms=:date,# default
date_col_market=:date,# default
add_intercept_col=true,# default
valuecols_firms=[:ret],# defaults to nothing, in which case
# all columns other than id and date are used
valuecols_market=[:mktrf, :rf, :smb, :hml, :umd]# defaults to
# nothing, in which case all columns other than date are used
)
MarketData with ID type Int64 with 99 unique firms
MarketCalendar: 2019-01-02 .. 2021-08-31 with 672 business days
Market Columns: hml, umd, intercept, mktrf, smb, rf
Firm Columns: ret
For performance, especially when loading large datasets of firm data, it is best to make sure the firm dataframe is presorted by ID then Date.
This object rearranges the data so it can be quickly accessed later. The mkt_data
now contains 3 things:
- A BusinessDays.jl calendar that exactly matches the days loaded in the market data.
- Each column of the
df_mkt
stored - Each column of the
df_firm
stored in aDict
for each firm.
Data is accessed on a by firm basis, for a given date range and specific columns. For example, say you wanted to get the data for Oracle (ORCL) ("Permno" of 10104), for a specific date (using IntervalSets.jl) and set of columns:
julia> orcl_data = mkt_data[10104, Date(2020) .. Date(2020, 6, 30), [:ret, :mktrf, :smb]]
125×3 FixedTable{3, Float64, SubArray{Float64, 1, OffsetArrays.OffsetVector{Float64, Vector{Float64}}, Tuple{UnitRange{Int64}}, true}, Symbol}: 0.0183088 0.0086 -0.0089 -0.00352182 -0.0067 0.0039 0.00520838 0.0036 -0.0007 0.00222056 -0.0019 -0.0001 0.00387742 0.0047 -0.0006 0.00461851 0.0065 -0.0064 0.00128723 -0.0034 -0.0018 0.00238753 0.0073 -0.0011 0.0054965 -0.0006 0.0038 -0.00218664 0.0016 0.0046 ⋮ 0.0421195 0.0019 0.0009 0.0132241 -0.0045 0.0009 0.0132352 0.0071 0.0083 0.00126995 0.0042 0.0016 -0.0135894 -0.0261 -0.0045 0.0016532 0.0112 0.0023 -0.00641846 -0.0244 0.0013 0.010705 0.0151 0.0126 0.00931341 0.0158 0.0008
Sometimes it is helpful to add a new column (either for convenience or performance reasons, discussed later). To do so, this package borrows the transform!
function from DataFrames.jl, using a formula where the left side is the column that is created:
julia> transform!(mkt_data, @formula(mkt ~ mktrf + rf));
It is also easy to specify the columns as a formula from StatsModels.jl. This allows for arbitrary functions, interactions and lags/leads:
julia> orcl_data = mkt_data[10104, Date(2020) .. Date(2020, 6, 30), @formula(ret ~ mkt + lag(mkt) + log1p(smb) * hml)]
125×7 FixedTable{7, Float64, SubArray{Float64, 1, OffsetArrays.OffsetVector{Float64, Vector{Float64}}, Tuple{UnitRange{Int64}}, true}, String}: 0.0183088 1.0 0.00866 0.00287 -0.00893984 -0.0032 2.86075e-5 -0.00352182 1.0 -0.00664 0.00866 0.00389241 0.0 0.0 0.00520838 1.0 0.00366 -0.00664 -0.000700245 -0.0054 3.78132e-6 0.00222056 1.0 -0.00184 0.00366 -0.000100005 -0.0025 2.50013e-7 0.00387742 1.0 0.00476 -0.00184 -0.00060018 -0.0065 3.90117e-6 0.00461851 1.0 0.00656 0.00476 -0.00642057 -0.0049 3.14608e-5 0.00128723 1.0 -0.00334 0.00656 -0.00180162 -0.0036 6.48584e-6 0.00238753 1.0 0.00736 -0.00334 -0.00110061 -0.0009 9.90545e-7 0.0054965 1.0 -0.00054 0.00736 0.0037928 -0.0016 -6.06848e-6 -0.00218664 1.0 0.00166 -0.00054 0.00458945 -0.008 -3.67156e-5 ⋮ ⋮ 0.0421195 1.0 0.0019 -0.004 0.000899595 -0.0053 -4.76785e-6 0.0132241 1.0 -0.0045 0.0019 0.000899595 -0.0065 -5.84737e-6 0.0132352 1.0 0.0071 -0.0045 0.00826574 -0.0133 -0.000109934 0.00126995 1.0 0.0042 0.0071 0.00159872 -0.006 -9.59233e-6 -0.0135894 1.0 -0.0261 0.0042 -0.00451016 -0.0136 6.13381e-5 0.0016532 1.0 0.0112 -0.0261 0.00229736 0.0053 1.2176e-5 -0.00641846 1.0 -0.0244 0.0112 0.00129916 -0.014 -1.81882e-5 0.010705 1.0 0.0151 -0.0244 0.0125213 0.0183 0.000229139 0.00931341 1.0 0.0158 0.0151 0.00079968 0.0001 7.9968e-8
While interactions and arbitrary functions are supported, they can significantly slow down performance since a new vector is allocated in each call. Therefore, it is generally recommended to create a new pice of data by calling transform!
on the dataset to create the new columns. This advice does not apply to lag/lead terms since those do not need to allocate a new column.
The data returned by accessing mkt_data
is a FixedTable
, which is essentially a matrix with a fixed width (useful for multiplication and returning a StaticMatrix
from StaticArrays.jl). Access into this data is done either by a slice as you would any other matrix:
julia> orcl_data[:, 1]
125-element view(OffsetArray(::Vector{Float64}, 1:505), 253:377) with eltype Float64: 0.018308818340301514 -0.0035218247212469578 0.0052083819173276424 0.002220557536929846 0.003877422772347927 0.004618511069566011 0.0012872322695329785 0.002387531101703644 0.005496504716575146 -0.0021866390015929937 ⋮ 0.042119529098272324 0.013224118389189243 0.013235245831310749 0.0012699509970843792 -0.01358941849321127 0.0016531989676877856 -0.006418457254767418 0.010705020278692245 0.00931340642273426
Or via the names used to access it in the first place:
julia> orcl_data[:, :mkt]
125-element view(OffsetArray(::Vector{Float64}, 1:672), 253:377) with eltype Float64: 0.00866 -0.00664 0.00366 -0.00184 0.0047599999999999995 0.006560000000000001 -0.0033399999999999997 0.00736 -0.0005399999999999999 0.00166 ⋮ 0.0019 -0.004500000000000001 0.0071 0.0042 -0.0261 0.0112 -0.0244 0.0151 0.0158
Estimating Regressions
The main goal of this package is quickly running regressions for firm events. The example used here is a firm's earnings announcement. Starting with one example, Oracle announced its Q3 2020 earnings on 2020-9-10. Calculating abnormal returns typically follows three steps:
- Estimate how the firm typically responds to market factors during a control (or estimation) window
- Use the coefficients from that regression to estimate how the firm should do during the event window
- Subtract the estimated return from the actual firm return during the event window. Depending on how this difference is aggregated, these are typically buy and hold abnormal returns (bhar) or cumulative abnormla returns (CAR)
First, to create the table for the estimation window, define an estimation window and an event window:
julia> event_date = Date("2020-09-10")
2020-09-10
julia> est_start = advancebdays(mkt_data.calendar, event_date, -120)
2020-03-20
julia> est_end = advancebdays(mkt_data.calendar, event_date, -2)
2020-09-08
julia> event_start = advancebdays(mkt_data.calendar, event_date, -1)
2020-09-09
julia> event_end = advancebdays(mkt_data.calendar, event_date, 1)
2020-09-11
Next, run the estimation regression (the regression automatically selects the correct columns from the data, so it is not necessary to do that beforehand):
orcl_data = mkt_data[10104, est_start .. est_end]
rr = quick_reg(orcl_data, @formula(ret ~ mkt + smb + hml))
Obs: 119, ret ~ -0.0 + 0.77*mkt + -0.302*smb + -0.012*hml, AdjR2: 52.744%
Then get the data for the event window:
julia> orcl_data = mkt_data[10104, event_start .. event_end];
Now it is easy to run some statistics for the event window:
julia> bhar(orcl_data, rr) # BHAR based on regression
0.026330396109940146
julia> car(orcl_data, rr) # CAR based on regression
0.02612077404623775
It is also easy to calculate some statistics for the estimation window:
julia> var(rr) # Variance of firm returns (similar equation for standard deviation)
0.00020283685456709137
julia> beta(rr) # Firm's market beta
0.7698040453871025
julia> alpha(rr) # Firm's market alpha
-0.0002921887656030498
More Data Using DataFramesMeta
While the above works well, abnormal returns are often calculated on thousands or more firm-events. Here, I use earnings announcements for about 100 firms from March to November 2020:
julia> df_events = CSV.File(joinpath(data_dir, "firm_earnings_announcements.csv")) |> DataFrame
283×2 DataFrame Row │ permno ea │ Int64 Date ─────┼──────────────────── 1 │ 49373 2020-03-05 2 │ 23660 2020-03-19 3 │ 17144 2020-03-18 4 │ 52708 2020-03-19 5 │ 57665 2020-03-24 6 │ 61621 2020-03-25 7 │ 10104 2020-03-12 8 │ 75154 2020-03-19 ⋮ │ ⋮ ⋮ 277 │ 90373 2020-10-29 278 │ 90386 2020-11-02 279 │ 90993 2020-10-29 280 │ 90880 2020-10-28 281 │ 91668 2020-10-30 282 │ 12449 2020-11-05 283 │ 61399 2020-11-18 268 rows omitted
Using DataFramesMeta.jl and the @chain
macro from Chain.jl, the above steps become:
df_events = @chain df_events begin
@rtransform(
:est_start = advancebdays(mkt_data.calendar, :ea, -120),
:est_end = advancebdays(mkt_data.calendar, :ea, -2),
:event_start = advancebdays(mkt_data.calendar, :ea, -1),
:event_end = advancebdays(mkt_data.calendar, :ea, 1),
)
@rtransform(:reg = quick_reg(mkt_data[:permno, :est_start .. :est_end], @formula(ret ~ mkt + smb + hml)))
@rtransform(
:bhar_reg = bhar(mkt_data[:permno, :event_start .. :event_end], :reg),
:bhar_simple = bhar(mkt_data[:permno, :event_start .. :event_end, ["ret", "mkt"]]),
:std = std(:reg),
:total_ret = bh_return(mkt_data[:permno, :event_start .. :event_end, ["ret"]]),
)
select(Not([:est_start, :est_end, :event_start, :event_end, :reg]))
end
283×6 DataFrame
Row │ permno ea bhar_reg bhar_simple std total_ret
│ Int64 Date Float64? Float64 Float64? Float64
─────┼────────────────────────────────────────────────────────────────────────
1 │ 49373 2020-03-05 -0.0114753 -0.0368966 0.0120205 -0.0494716
2 │ 23660 2020-03-19 -0.0761877 -0.0799651 0.0100327 -0.16273
3 │ 17144 2020-03-18 0.00584866 -0.00749583 0.0122784 0.00562007
4 │ 52708 2020-03-19 0.0449673 0.057314 0.023234 -0.0254504
5 │ 57665 2020-03-24 0.105811 0.0931054 0.0105352 0.171386
6 │ 61621 2020-03-25 0.125142 0.129935 0.0132682 0.303036
7 │ 10104 2020-03-12 0.0295405 0.0515017 0.00903142 -0.01338
8 │ 75154 2020-03-19 0.15375 0.0269029 0.0312084 -0.0558615
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
277 │ 90373 2020-10-29 -0.00714584 -0.00486512 0.016151 -0.0422143
278 │ 90386 2020-11-02 -0.120063 -0.0769287 0.0256068 -0.0606557
279 │ 90993 2020-10-29 -0.00087777 0.00495886 0.0109232 -0.0323903
280 │ 90880 2020-10-28 0.00943539 -0.00174027 0.0109558 -0.0271723
281 │ 91668 2020-10-30 0.0290468 0.0200758 0.017888 0.0283726
282 │ 12449 2020-11-05 -0.0402344 -0.0572696 0.0183221 -0.0128859
283 │ 61399 2020-11-18 -0.0761987 -0.0706244 0.0134081 -0.0759727
268 rows omitted
Vectorizing the Data
While the above works, and is reasonably fast (Doing a test on 1 million regressions takes about 26 seconds on a Ryzen 7 5700X), faster is better.
In particular, a significant reason the above is slow method is that the formula is parsed for each iteration. If the formula is the same for all of the cases, it is better if it is simply parsed once. Therefore, it is optimal to do as much as possible using vectors.
To make this possible, this package provides a type IterateFixedTable
which will return a FixedTable
based on a supplied set of ids, dates and columns (or formula as above):
julia> est_starts = advancebdays.(mkt_data.calendar, df_events.ea, -120)
283-element Vector{Dates.Date}: 2019-09-12 2019-09-26 2019-09-25 2019-09-26 2019-10-01 2019-10-02 2019-09-19 2019-09-26 2019-09-10 2019-11-13 ⋮ 2020-05-13 2020-05-07 2020-05-11 2020-05-13 2020-05-11 2020-05-08 2020-05-12 2020-05-18 2020-06-01
julia> est_ends = advancebdays.(mkt_data.calendar, df_events.ea, -2)
283-element Vector{Dates.Date}: 2020-03-03 2020-03-17 2020-03-16 2020-03-17 2020-03-20 2020-03-23 2020-03-10 2020-03-17 2020-02-28 2020-05-05 ⋮ 2020-10-29 2020-10-23 2020-10-27 2020-10-29 2020-10-27 2020-10-26 2020-10-28 2020-11-03 2020-11-16
julia> vec_data = mkt_data[df_events.permno, est_starts .. est_ends, [:ret, :mkt, :smb]]
Iterable set of FixedTable with 283 unique datapoints MarketData with ID type Int64 with 99 unique firms MarketCalendar: 2019-01-02 .. 2021-08-31 with 672 business days Market Columns: mkt, hml, umd, intercept, mktrf, smb, rf Firm Columns: ret
Each element of vec_data
is then easily accessible by an integer or can be looped over in a for loop:
# for x in vec_data
# x
# end
# or
vec_data[10]
119×3 FixedTable{3, Float64, SubArray{Float64, 1, OffsetArrays.OffsetVector{Float64, Vector{Float64}}, Tuple{UnitRange{Int64}}, true}, Symbol}:
-0.0395488 0.00016 -0.0022
-0.016239 0.00076 -0.001
0.0025055 0.00746 -0.0026
-0.0180829 0.00026 -0.0035
-0.00883372 0.00026 0.0058
0.0140483 -0.00324 0.0
-0.00327713 -0.00134 -0.0036
-0.0379615 0.00246 0.0005
0.00590333 0.00926 0.0132
-0.0182239 0.00196 0.0006
⋮
0.0581513 0.0013 0.0112
0.0189585 0.0144 0.0027
0.0167216 0.0173 0.0161
0.0261756 -0.0045 0.01
0.106321 0.0292 0.0188
-0.00754946 -0.0118 -0.0142
-0.0896382 -0.0291 -0.0056
0.045393 0.0053 0.0018
-0.00324047 0.0095 0.0004
This object can be similarly passed to the above functions, just like a firm level table. The function will iterate through the data and return a vector of results.
However, the above is rather ugly. A more practical way to use this is to continue using the @chain
macro:
df_events = @chain df_events begin
@rtransform(
:est_start = advancebdays(mkt_data.calendar, :ea, -120),
:est_end = advancebdays(mkt_data.calendar, :ea, -2),
:event_start = advancebdays(mkt_data.calendar, :ea, -1),
:event_end = advancebdays(mkt_data.calendar, :ea, 1),
)
@transform(:reg = quick_reg(mkt_data[:permno, :est_start .. :est_end], @formula(ret ~ mkt + smb + hml)))
@transform(
:bhar_reg = bhar(mkt_data[:permno, :event_start .. :event_end], :reg),
:bhar_simple = bhar(mkt_data[:permno, :event_start .. :event_end, ["ret", "mkt"]]),
)
@transform(
:std = std.(:reg),
:total_ret = bh_return(mkt_data[:permno, :event_start .. :event_end, ["ret"]]),
)
select(Not([:est_start, :est_end, :event_start, :event_end, :reg]))
end
283×6 DataFrame
Row │ permno ea bhar_reg bhar_simple std total_ret
│ Int64 Date Float64? Float64 Float64? Float64
─────┼────────────────────────────────────────────────────────────────────────
1 │ 49373 2020-03-05 -0.0114753 -0.0368966 0.0120205 -0.0494716
2 │ 23660 2020-03-19 -0.0761877 -0.0799651 0.0100327 -0.16273
3 │ 17144 2020-03-18 0.00584866 -0.00749583 0.0122784 0.00562007
4 │ 52708 2020-03-19 0.0449673 0.057314 0.023234 -0.0254504
5 │ 57665 2020-03-24 0.105811 0.0931054 0.0105352 0.171386
6 │ 61621 2020-03-25 0.125142 0.129935 0.0132682 0.303036
7 │ 10104 2020-03-12 0.0295405 0.0515017 0.00903142 -0.01338
8 │ 75154 2020-03-19 0.15375 0.0269029 0.0312084 -0.0558615
⋮ │ ⋮ ⋮ ⋮ ⋮ ⋮ ⋮
277 │ 90373 2020-10-29 -0.00714584 -0.00486512 0.016151 -0.0422143
278 │ 90386 2020-11-02 -0.120063 -0.0769287 0.0256068 -0.0606557
279 │ 90993 2020-10-29 -0.00087777 0.00495886 0.0109232 -0.0323903
280 │ 90880 2020-10-28 0.00943539 -0.00174027 0.0109558 -0.0271723
281 │ 91668 2020-10-30 0.0290468 0.0200758 0.017888 0.0283726
282 │ 12449 2020-11-05 -0.0402344 -0.0572696 0.0183221 -0.0128859
283 │ 61399 2020-11-18 -0.0761987 -0.0706244 0.0134081 -0.0759727
268 rows omitted
Notice that the only difference between these two @chain
macros is that this one uses @transform
instead of @rtransform
. This sends the entire column vector to the function, and allows for much faster overall results. Those same 1 million regressions now takes just 0.44 seconds on the same computer.