Introduction
A nifty .NET library, ‘R.NET‘, allows you to leverage previously written R scripts within your C# and other .NET applications. Experienced model developers often end up accumulating extremely useful R scripts which have been optimized to perform specific tasks. When you have a library of codes that you trust to perform operations as you expect, the knowledge that you can re-use the codes regardless of the development platform is invaluable. This is one of the benefits R.NET provides.
R.NET is just one of several method you can use to establish an interface between C# (.NET). The advantage of R. R.NET is that it enables the .NET Framework to ‘interoperate’ with the R statistical language in the same process (this is important because it prevents bulky code) . Also, the syntax is simple enough that anyone who has a little experience with both R and .NET products can pretty easily use it.
Instead of the customary “Hello World” example, we present a realistic example where we can call R functions, pass arguments to those functions and return the output to your C# application. This is a practical example because when model developers hand-off their model codes to developers in the IT department, ideally they want minimal changes to the code. This can potentially save everyone a bunch of headaches.
R is a platform and a programming language that has been optimized for statistical computing and graphics. It would make sense to allow the R “engine” to handle the statistical and machine learning operations. It allows the IT team to plug in tried and true R scripts directly into larger applications. To illustrate, we will use functions from the CreditMetrics package from R.
CreditMetrics is a Credit Value-at-Risk (C-VaR) model used to measure credit risk in a portfolio context. This is a structural model of default, which also takes into account the risk of credit deterioration. Losses therefore do not occur only in the case of the counterparty’s default, but also upon its transition into worse rating categories. The CreditMetrics model also takes into account dependence among defaults, in particular, a Gaussian copula.
Sample Code
All of the R scripts used in this blog are publicly available. Both GenerateRandomPortfolio.R and ContinuousTimeMarkovChain.R were borrowed from https://www.qrmtutorial.org/r-code while CreditMetrics.R was borrowed from https://cran.r-project.org/web/packages/CreditMetrics/index.html. A few modifications to the R scripts were necessary to get them in the correct format for this example. Specifically, it is required to convert any R script you want to call from C# into a function.
The exact R scripts and C# codes used for this example can be downloaded here ( RScipts.7z ) and ( RtoCSharp.7z).
To replicate this example please click here to download and install R.NET.
C# Code
The code below uses an object oriented construct in C#. The first part of the code, ‘Main’, presents the data preparation stage.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using RDotNet; namespace RtoCSharp { class Program { static void Main(string[] args) { REngine.SetEnvironmentVariables(); REngine engine = REngine.GetInstance(); engine.Initialize(); CharacterVector BorrowerNames = engine.CreateCharacterVector(new string[] { "Skoont", "Kyler", "Chip"}); NumericVector exposureCount = engine.CreateNumericVector(new double[] { 3}); NumericVector simNumber = engine.CreateNumericVector(new double[] { 10}); NumericVector riskLessRate = engine.CreateNumericVector(new double[] { 0.03}); NumericVector EAD_per_Exposure = engine.CreateNumericVector(new double[] { 4000000, 1000000, 10000000}); NumericVector OverallLGD = engine.CreateNumericVector(new double[] { 0.45}); NumericVector corr = engine.CreateNumericVector(new double[] { 0.4}); CharacterVector BorrowerRatings = engine.CreateCharacterVector(new string[] { "B1", "A2", "B3"}); NumericVector ConfidenceLevel = engine.CreateNumericVector(new double[] { 0.99}); NumericMatrix rho = engine.CreateNumericMatrix(new double[,] {{1.0, 0.4, 0.6}, { 0.4, 1, 0.5}, { 0.6, 0.5, 1}}); CharacterVector CopulaType = engine.CreateCharacterVector(new string[] { "Student"}); NumericVector df = engine.CreateNumericVector(new double[] { 10}); string pathRCode = "C:/Users/R Code"; //Create an instance of clsRtoCS clsRtoCS rtoCS = new clsRtoCS(); //randomly generate a hypothetical portfolio DataFrame getPortfolio = rtoCS.RandomPortfolio(pathRCode); //create transition matrix using the hypothetical portfolio (continuous time markov chain) NumericMatrix CTMC = rtoCS.getTransitionMatrix(getPortfolio, pathRCode); //run the CreditMetrics NumericMatrix cm_CVaR = rtoCS.runCreditMetrics(CTMC, exposureCount, simNumber, riskLessRate, EAD_per_Exposure, OverallLGD, corr, BorrowerRatings, ConfidenceLevel, rho, CopulaType, df, pathRCode); Console.WriteLine(cm_CVaR); engine.Dispose(); } } }
The second part of the code creates a class that interacts with R.NET to pass variables to R scripts and accepts the return values.
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using RDotNet; namespace RtoCSharp { class clsRtoCS { REngine engine = REngine.GetInstance(); public NumericMatrix runCreditMetrics(NumericMatrix CTMC, NumericVector exposureCount, NumericVector simNumber, NumericVector riskLessRate, NumericVector EAD_per_Exposure, NumericVector OverallLGD, NumericVector corr, CharacterVector BorrowerRatings, NumericVector ConfidenceLevel, NumericMatrix rho, CharacterVector CopulaType, NumericVector df, string getPath) { engine.Initialize(); engine.SetSymbol("CTMC", CTMC); engine.SetSymbol("exposureCount", exposureCount); engine.SetSymbol("simNumber", simNumber); engine.SetSymbol("riskLessRate", riskLessRate); engine.SetSymbol("EAD_per_Exposure", EAD_per_Exposure); engine.SetSymbol("OverallLGD", OverallLGD); engine.SetSymbol("rho", rho); engine.SetSymbol("BorrowerRatings", BorrowerRatings); engine.SetSymbol("ConfidenceLevel", ConfidenceLevel); engine.SetSymbol("CopulaType", CopulaType); engine.SetSymbol("df", df); string completePath = "source('" + getPath + "/CreditMetrics.R')"; engine.Evaluate(completePath); engine.Dispose(); NumericMatrix cm_CVaR = engine.Evaluate(" cm.CVaR(CTMC,OverallLGD,EAD_per_Exposure,exposureCount,simNumber,riskLessRate,rho," + "ConfidenceLevel,BorrowerRatings,CopulaType, df)").AsNumericMatrix(); return cm_CVaR; } //Here we randomly generate a portfolio public DataFrame RandomPortfolio(string getPath) { engine.Initialize(); string completePath = "source('" + getPath + "/GenerateRandomPortfolio.R')"; engine.Evaluate(completePath); DataFrame getPortfolio = engine.Evaluate("GenerateRandomPortfolio()").AsDataFrame(); engine.Dispose(); return getPortfolio; } //use random portfolio to generate a continuous time markov chain public NumericMatrix getTransitionMatrix(DataFrame getPortfolio, string getPath) { engine.Initialize(); engine.SetSymbol("getPortfolio", getPortfolio); //convert to (getPortfolio is the randomly generated portfolio) string completePath = "source('" + getPath + "/ContinuousTimeMarkovChain.R')"; engine.Evaluate(completePath); NumericMatrix CTMC = engine.Evaluate("ContinuousTimeMarkovChain(getPortfolio)").AsNumericMatrix(); engine.Dispose(); return CTMC; } } }