Skip to main content

sklears_utils/
r_integration.rs

1//! R integration utilities
2//!
3//! This module provides utilities for R integration including data exchange,
4//! R script execution, statistical function bindings, and package management.
5
6use std::collections::HashMap;
7use std::fmt::Write;
8use std::fs;
9use std::process::Command;
10
11/// R integration utilities
12pub struct RIntegration {
13    #[allow(dead_code)]
14    r_home: Option<String>,
15    #[allow(dead_code)]
16    library_paths: Vec<String>,
17    loaded_packages: Vec<String>,
18    workspace_variables: HashMap<String, RValue>,
19}
20
21/// R value types
22#[derive(Debug, Clone)]
23pub enum RValue {
24    Null,
25    Logical(bool),
26    Integer(i32),
27    Double(f64),
28    Character(String),
29    IntegerVector(Vec<i32>),
30    DoubleVector(Vec<f64>),
31    CharacterVector(Vec<String>),
32    LogicalVector(Vec<bool>),
33    Matrix {
34        data: Vec<f64>,
35        nrows: usize,
36        ncols: usize,
37    },
38    DataFrame {
39        columns: HashMap<String, RValue>,
40        nrows: usize,
41    },
42    List(HashMap<String, RValue>),
43}
44
45/// R data frame representation
46#[derive(Debug, Clone)]
47pub struct RDataFrame {
48    pub columns: HashMap<String, RValue>,
49    pub nrows: usize,
50    pub column_names: Vec<String>,
51}
52
53/// R matrix representation
54#[derive(Debug, Clone)]
55pub struct RMatrix {
56    pub data: Vec<f64>,
57    pub nrows: usize,
58    pub ncols: usize,
59    pub row_names: Option<Vec<String>>,
60    pub col_names: Option<Vec<String>>,
61}
62
63/// R script builder
64pub struct RScriptBuilder {
65    script_lines: Vec<String>,
66    variables: HashMap<String, RValue>,
67    packages: Vec<String>,
68}
69
70/// R package manager
71pub struct RPackageManager {
72    installed_packages: Vec<String>,
73    #[allow(dead_code)]
74    available_packages: HashMap<String, String>, // name -> version
75    cran_mirror: String,
76}
77
78/// R statistical functions
79pub struct RStatisticalFunctions;
80
81impl RIntegration {
82    /// Create new R integration instance
83    pub fn new() -> Result<Self, RError> {
84        let r_home = Self::detect_r_installation()?;
85
86        Ok(Self {
87            r_home: Some(r_home),
88            library_paths: vec![],
89            loaded_packages: ["base", "stats", "utils", "graphics", "grDevices"]
90                .iter()
91                .map(|s| s.to_string())
92                .collect(),
93            workspace_variables: HashMap::new(),
94        })
95    }
96
97    /// Detect R installation
98    fn detect_r_installation() -> Result<String, RError> {
99        // Try to find R executable
100        let output = Command::new("R")
101            .args(["--slave", "--vanilla", "-e", "cat(R.home())"])
102            .output()
103            .map_err(|_| RError::RNotFound)?;
104
105        if output.status.success() {
106            String::from_utf8(output.stdout).map_err(|_| RError::InvalidOutput)
107        } else {
108            Err(RError::RNotFound)
109        }
110    }
111
112    /// Execute R script
113    pub fn execute_script(&mut self, script: &str) -> Result<String, RError> {
114        // Write script to temporary file
115        let temp_path = std::env::temp_dir().join("sklears_r_script.R");
116        let temp_file = temp_path.to_string_lossy().into_owned();
117        fs::write(&temp_file, script).map_err(|e| RError::IoError(e.to_string()))?;
118
119        // Execute R script
120        let output = Command::new("Rscript")
121            .arg(&temp_file)
122            .output()
123            .map_err(|e| RError::ExecutionError(e.to_string()))?;
124
125        // Clean up
126        let _ = fs::remove_file(&temp_file);
127
128        if output.status.success() {
129            String::from_utf8(output.stdout).map_err(|_| RError::InvalidOutput)
130        } else {
131            let error_msg =
132                String::from_utf8(output.stderr).unwrap_or_else(|_| "Unknown R error".to_string());
133            Err(RError::RScriptError(error_msg))
134        }
135    }
136
137    /// Load R package
138    pub fn load_package(&mut self, package_name: &str) -> Result<(), RError> {
139        let script = format!("library({package_name})");
140        self.execute_script(&script)?;
141        self.loaded_packages.push(package_name.to_string());
142        Ok(())
143    }
144
145    /// Convert Rust array to R vector
146    pub fn array_to_r_vector(&self, data: &[f64]) -> RValue {
147        RValue::DoubleVector(data.to_vec())
148    }
149
150    /// Convert Rust matrix to R matrix
151    pub fn matrix_to_r_matrix(&self, data: &[f64], nrows: usize, ncols: usize) -> RValue {
152        RValue::Matrix {
153            data: data.to_vec(),
154            nrows,
155            ncols,
156        }
157    }
158
159    /// Convert R value to Rust array
160    pub fn r_vector_to_array(&self, r_value: &RValue) -> Result<Vec<f64>, RError> {
161        match r_value {
162            RValue::DoubleVector(vec) => Ok(vec.clone()),
163            RValue::IntegerVector(vec) => Ok(vec.iter().map(|&x| x as f64).collect()),
164            _ => Err(RError::TypeMismatch),
165        }
166    }
167
168    /// Create R data frame from columns
169    pub fn create_dataframe(&self, columns: HashMap<String, RValue>) -> Result<RDataFrame, RError> {
170        // Validate all columns have same length
171        let mut nrows = 0;
172        let mut column_names = Vec::new();
173
174        for (name, value) in &columns {
175            let length = match value {
176                RValue::IntegerVector(v) => v.len(),
177                RValue::DoubleVector(v) => v.len(),
178                RValue::CharacterVector(v) => v.len(),
179                RValue::LogicalVector(v) => v.len(),
180                _ => return Err(RError::InvalidDataFrame),
181            };
182
183            if nrows == 0 {
184                nrows = length;
185            } else if nrows != length {
186                return Err(RError::InvalidDataFrame);
187            }
188
189            column_names.push(name.clone());
190        }
191
192        Ok(RDataFrame {
193            columns,
194            nrows,
195            column_names,
196        })
197    }
198
199    /// Execute R statistical function
200    pub fn call_r_function(
201        &mut self,
202        function_name: &str,
203        args: &[RValue],
204    ) -> Result<RValue, RError> {
205        let mut script = String::new();
206
207        // Convert arguments to R syntax
208        for (i, arg) in args.iter().enumerate() {
209            let var_name = format!("arg{i}");
210            let r_code = self.r_value_to_r_code(arg)?;
211            writeln!(script, "{var_name} <- {r_code}")
212                .map_err(|e| RError::ScriptGenerationError(e.to_string()))?;
213        }
214
215        // Call function
216        let arg_names: Vec<String> = (0..args.len()).map(|i| format!("arg{i}")).collect();
217        writeln!(
218            script,
219            "result <- {}({})",
220            function_name,
221            arg_names.join(", ")
222        )
223        .map_err(|e| RError::ScriptGenerationError(e.to_string()))?;
224
225        // Output result
226        writeln!(script, "cat(paste(result, collapse=','))")
227            .map_err(|e| RError::ScriptGenerationError(e.to_string()))?;
228
229        let output = self.execute_script(&script)?;
230        self.parse_r_output(&output)
231    }
232
233    /// Convert R value to R code
234    fn r_value_to_r_code(&self, value: &RValue) -> Result<String, RError> {
235        match value {
236            RValue::Null => Ok("NULL".to_string()),
237            RValue::Logical(b) => Ok(if *b { "TRUE" } else { "FALSE" }.to_string()),
238            RValue::Integer(i) => Ok(format!("{i}L")),
239            RValue::Double(d) => Ok(d.to_string()),
240            RValue::Character(s) => Ok(format!("\"{}\"", s.replace("\"", "\\\""))),
241            RValue::IntegerVector(vec) => Ok(format!(
242                "c({})",
243                vec.iter()
244                    .map(|x| format!("{x}L"))
245                    .collect::<Vec<_>>()
246                    .join(", ")
247            )),
248            RValue::DoubleVector(vec) => Ok(format!(
249                "c({})",
250                vec.iter()
251                    .map(|x| x.to_string())
252                    .collect::<Vec<_>>()
253                    .join(", ")
254            )),
255            RValue::CharacterVector(vec) => Ok(format!(
256                "c({})",
257                vec.iter()
258                    .map(|s| format!("\"{}\"", s.replace("\"", "\\\"")))
259                    .collect::<Vec<_>>()
260                    .join(", ")
261            )),
262            RValue::LogicalVector(vec) => Ok(format!(
263                "c({})",
264                vec.iter()
265                    .map(|b| if *b { "TRUE" } else { "FALSE" })
266                    .collect::<Vec<_>>()
267                    .join(", ")
268            )),
269            RValue::Matrix { data, nrows, ncols } => Ok(format!(
270                "matrix(c({}), nrow={}, ncol={})",
271                data.iter()
272                    .map(|x| x.to_string())
273                    .collect::<Vec<_>>()
274                    .join(", "),
275                nrows,
276                ncols
277            )),
278            _ => Err(RError::UnsupportedType),
279        }
280    }
281
282    /// Parse R output
283    fn parse_r_output(&self, output: &str) -> Result<RValue, RError> {
284        let trimmed = output.trim();
285
286        if trimmed.is_empty() {
287            return Ok(RValue::Null);
288        }
289
290        // Try to parse as comma-separated values
291        if trimmed.contains(',') {
292            let values: Result<Vec<f64>, _> = trimmed
293                .split(',')
294                .map(|s| s.trim().parse::<f64>())
295                .collect();
296
297            if let Ok(vec) = values {
298                return Ok(RValue::DoubleVector(vec));
299            }
300        }
301
302        // Try to parse as single value
303        if let Ok(value) = trimmed.parse::<f64>() {
304            return Ok(RValue::Double(value));
305        }
306
307        if let Ok(value) = trimmed.parse::<i32>() {
308            return Ok(RValue::Integer(value));
309        }
310
311        if trimmed == "TRUE" {
312            return Ok(RValue::Logical(true));
313        }
314
315        if trimmed == "FALSE" {
316            return Ok(RValue::Logical(false));
317        }
318
319        // Default to character
320        Ok(RValue::Character(trimmed.to_string()))
321    }
322
323    /// Get loaded packages
324    pub fn get_loaded_packages(&self) -> &[String] {
325        &self.loaded_packages
326    }
327
328    /// Check if package is available
329    pub fn is_package_available(&mut self, package_name: &str) -> Result<bool, RError> {
330        let script = format!("cat(is.element('{package_name}', installed.packages()[,1]))");
331        let output = self.execute_script(&script)?;
332        Ok(output.trim() == "TRUE")
333    }
334
335    /// Install R package
336    pub fn install_package(&mut self, package_name: &str) -> Result<(), RError> {
337        let script =
338            format!("install.packages('{package_name}', repos='https://cran.r-project.org')");
339        self.execute_script(&script)?;
340        Ok(())
341    }
342
343    /// Save workspace variable
344    pub fn save_variable(&mut self, name: &str, value: RValue) {
345        self.workspace_variables.insert(name.to_string(), value);
346    }
347
348    /// Get workspace variable
349    pub fn get_variable(&self, name: &str) -> Option<&RValue> {
350        self.workspace_variables.get(name)
351    }
352
353    /// Clear workspace
354    pub fn clear_workspace(&mut self) {
355        self.workspace_variables.clear();
356    }
357}
358
359impl RScriptBuilder {
360    /// Create new R script builder
361    pub fn new() -> Self {
362        Self {
363            script_lines: Vec::new(),
364            variables: HashMap::new(),
365            packages: Vec::new(),
366        }
367    }
368
369    /// Add package requirement
370    pub fn require_package(&mut self, package: &str) -> &mut Self {
371        self.packages.push(package.to_string());
372        self
373    }
374
375    /// Add variable assignment
376    pub fn assign_variable(&mut self, name: &str, value: RValue) -> &mut Self {
377        self.variables.insert(name.to_string(), value);
378        self
379    }
380
381    /// Add R code line
382    pub fn add_line(&mut self, line: &str) -> &mut Self {
383        self.script_lines.push(line.to_string());
384        self
385    }
386
387    /// Add comment
388    pub fn add_comment(&mut self, comment: &str) -> &mut Self {
389        self.script_lines.push(format!("# {comment}"));
390        self
391    }
392
393    /// Build the complete R script
394    pub fn build(&self) -> Result<String, RError> {
395        let mut script = String::new();
396
397        // Add package loading
398        for package in &self.packages {
399            writeln!(script, "library({package})")
400                .map_err(|e| RError::ScriptGenerationError(e.to_string()))?;
401        }
402
403        if !self.packages.is_empty() {
404            writeln!(script).map_err(|e| RError::ScriptGenerationError(e.to_string()))?;
405        }
406
407        // Add variable assignments
408        for (name, value) in &self.variables {
409            let r_code = self.r_value_to_r_code(value)?;
410            writeln!(script, "{name} <- {r_code}")
411                .map_err(|e| RError::ScriptGenerationError(e.to_string()))?;
412        }
413
414        if !self.variables.is_empty() {
415            writeln!(script).map_err(|e| RError::ScriptGenerationError(e.to_string()))?;
416        }
417
418        // Add script lines
419        for line in &self.script_lines {
420            writeln!(script, "{line}").map_err(|e| RError::ScriptGenerationError(e.to_string()))?;
421        }
422
423        Ok(script)
424    }
425
426    /// Convert R value to R code (helper method)
427    fn r_value_to_r_code(&self, value: &RValue) -> Result<String, RError> {
428        match value {
429            RValue::Null => Ok("NULL".to_string()),
430            RValue::Logical(b) => Ok(if *b { "TRUE" } else { "FALSE" }.to_string()),
431            RValue::Integer(i) => Ok(format!("{i}L")),
432            RValue::Double(d) => Ok(d.to_string()),
433            RValue::Character(s) => Ok(format!("\"{}\"", s.replace("\"", "\\\""))),
434            RValue::DoubleVector(vec) => Ok(format!(
435                "c({})",
436                vec.iter()
437                    .map(|x| x.to_string())
438                    .collect::<Vec<_>>()
439                    .join(", ")
440            )),
441            _ => Err(RError::UnsupportedType),
442        }
443    }
444}
445
446impl RPackageManager {
447    /// Create new R package manager
448    pub fn new() -> Self {
449        Self {
450            installed_packages: Vec::new(),
451            available_packages: HashMap::new(),
452            cran_mirror: "https://cran.r-project.org".to_string(),
453        }
454    }
455
456    /// Refresh package information
457    pub fn refresh(&mut self) -> Result<(), RError> {
458        // Get installed packages
459        let script = "cat(paste(installed.packages()[,1], collapse=','))";
460        let output = Command::new("Rscript")
461            .args(["-e", script])
462            .output()
463            .map_err(|e| RError::ExecutionError(e.to_string()))?;
464
465        if output.status.success() {
466            let packages_str =
467                String::from_utf8(output.stdout).map_err(|_| RError::InvalidOutput)?;
468
469            self.installed_packages = packages_str
470                .trim()
471                .split(',')
472                .map(|s| s.trim().to_string())
473                .filter(|s| !s.is_empty())
474                .collect();
475        }
476
477        Ok(())
478    }
479
480    /// Check if package is installed
481    pub fn is_installed(&self, package_name: &str) -> bool {
482        self.installed_packages.contains(&package_name.to_string())
483    }
484
485    /// Install package
486    pub fn install(&mut self, package_name: &str) -> Result<(), RError> {
487        let script = format!(
488            "install.packages('{}', repos='{}')",
489            package_name, self.cran_mirror
490        );
491
492        let output = Command::new("Rscript")
493            .args(["-e", &script])
494            .output()
495            .map_err(|e| RError::ExecutionError(e.to_string()))?;
496
497        if output.status.success() {
498            self.installed_packages.push(package_name.to_string());
499            Ok(())
500        } else {
501            let error_msg = String::from_utf8(output.stderr)
502                .unwrap_or_else(|_| "Package installation failed".to_string());
503            Err(RError::PackageInstallationError(error_msg))
504        }
505    }
506
507    /// Remove package
508    pub fn remove(&mut self, package_name: &str) -> Result<(), RError> {
509        let script = format!("remove.packages('{package_name}')");
510
511        let output = Command::new("Rscript")
512            .args(["-e", &script])
513            .output()
514            .map_err(|e| RError::ExecutionError(e.to_string()))?;
515
516        if output.status.success() {
517            self.installed_packages.retain(|p| p != package_name);
518            Ok(())
519        } else {
520            let error_msg = String::from_utf8(output.stderr)
521                .unwrap_or_else(|_| "Package removal failed".to_string());
522            Err(RError::PackageRemovalError(error_msg))
523        }
524    }
525
526    /// Get installed packages
527    pub fn get_installed_packages(&self) -> &[String] {
528        &self.installed_packages
529    }
530
531    /// Set CRAN mirror
532    pub fn set_cran_mirror(&mut self, mirror_url: &str) {
533        self.cran_mirror = mirror_url.to_string();
534    }
535}
536
537impl RStatisticalFunctions {
538    /// Compute mean using R
539    pub fn mean(r: &mut RIntegration, data: &[f64]) -> Result<f64, RError> {
540        let r_vector = r.array_to_r_vector(data);
541        let result = r.call_r_function("mean", &[r_vector])?;
542
543        match result {
544            RValue::Double(mean_val) => Ok(mean_val),
545            _ => Err(RError::TypeMismatch),
546        }
547    }
548
549    /// Compute standard deviation using R
550    pub fn sd(r: &mut RIntegration, data: &[f64]) -> Result<f64, RError> {
551        let r_vector = r.array_to_r_vector(data);
552        let result = r.call_r_function("sd", &[r_vector])?;
553
554        match result {
555            RValue::Double(sd_val) => Ok(sd_val),
556            _ => Err(RError::TypeMismatch),
557        }
558    }
559
560    /// Perform t-test using R
561    pub fn t_test(r: &mut RIntegration, x: &[f64], y: &[f64]) -> Result<RValue, RError> {
562        r.load_package("stats")?;
563
564        let x_vector = r.array_to_r_vector(x);
565        let y_vector = r.array_to_r_vector(y);
566
567        r.call_r_function("t.test", &[x_vector, y_vector])
568    }
569
570    /// Perform linear regression using R
571    pub fn lm(r: &mut RIntegration, x: &[f64], y: &[f64]) -> Result<RValue, RError> {
572        r.load_package("stats")?;
573
574        // Create data frame
575        let mut columns = HashMap::new();
576        columns.insert("x".to_string(), r.array_to_r_vector(x));
577        columns.insert("y".to_string(), r.array_to_r_vector(y));
578
579        let script = "
580data <- data.frame(x=arg0, y=arg1)
581model <- lm(y ~ x, data=data)
582coefficients <- coef(model)
583cat(paste(coefficients, collapse=','))
584";
585
586        let mut script_with_args = String::new();
587        writeln!(
588            script_with_args,
589            "arg0 <- {}",
590            r.r_value_to_r_code(&columns["x"])?
591        )
592        .expect("operation should succeed");
593        writeln!(
594            script_with_args,
595            "arg1 <- {}",
596            r.r_value_to_r_code(&columns["y"])?
597        )
598        .expect("operation should succeed");
599        script_with_args.push_str(script);
600
601        let output = r.execute_script(&script_with_args)?;
602        r.parse_r_output(&output)
603    }
604
605    /// Compute correlation using R
606    pub fn cor(r: &mut RIntegration, x: &[f64], y: &[f64]) -> Result<f64, RError> {
607        let x_vector = r.array_to_r_vector(x);
608        let y_vector = r.array_to_r_vector(y);
609        let result = r.call_r_function("cor", &[x_vector, y_vector])?;
610
611        match result {
612            RValue::Double(cor_val) => Ok(cor_val),
613            _ => Err(RError::TypeMismatch),
614        }
615    }
616
617    /// Perform ANOVA using R
618    pub fn anova(r: &mut RIntegration, groups: &[Vec<f64>]) -> Result<RValue, RError> {
619        r.load_package("stats")?;
620
621        // Prepare data for ANOVA
622        let mut script = String::new();
623        let mut all_values = Vec::new();
624        let mut group_labels = Vec::new();
625
626        for (i, group) in groups.iter().enumerate() {
627            for &value in group {
628                all_values.push(value);
629                let group_num = i + 1;
630                group_labels.push(format!("Group{group_num}"));
631            }
632        }
633
634        writeln!(
635            script,
636            "values <- c({})",
637            all_values
638                .iter()
639                .map(|x| x.to_string())
640                .collect::<Vec<_>>()
641                .join(", ")
642        )
643        .expect("operation should succeed");
644        writeln!(
645            script,
646            "groups <- factor(c({}))",
647            group_labels
648                .iter()
649                .map(|s| format!("\"{s}\""))
650                .collect::<Vec<_>>()
651                .join(", ")
652        )
653        .expect("operation should succeed");
654        writeln!(script, "result <- aov(values ~ groups)").expect("operation should succeed");
655        writeln!(script, "summary_result <- summary(result)").expect("operation should succeed");
656        writeln!(script, "cat('ANOVA completed')").expect("operation should succeed");
657
658        let output = r.execute_script(&script)?;
659        Ok(RValue::Character(output))
660    }
661
662    /// Generate R plots
663    pub fn plot(r: &mut RIntegration, x: &[f64], y: &[f64], filename: &str) -> Result<(), RError> {
664        let script = format!(
665            "
666x <- c({})
667y <- c({})
668png('{}')
669plot(x, y, main='Scatter Plot', xlab='X', ylab='Y')
670dev.off()
671",
672            x.iter()
673                .map(|v| v.to_string())
674                .collect::<Vec<_>>()
675                .join(", "),
676            y.iter()
677                .map(|v| v.to_string())
678                .collect::<Vec<_>>()
679                .join(", "),
680            filename
681        );
682
683        r.execute_script(&script)?;
684        Ok(())
685    }
686}
687
688/// R integration errors
689#[derive(Debug, thiserror::Error)]
690pub enum RError {
691    #[error("R installation not found")]
692    RNotFound,
693    #[error("Invalid R output")]
694    InvalidOutput,
695    #[error("I/O error: {0}")]
696    IoError(String),
697    #[error("R script execution error: {0}")]
698    ExecutionError(String),
699    #[error("R script error: {0}")]
700    RScriptError(String),
701    #[error("Type mismatch")]
702    TypeMismatch,
703    #[error("Invalid data frame")]
704    InvalidDataFrame,
705    #[error("Unsupported type")]
706    UnsupportedType,
707    #[error("Script generation error: {0}")]
708    ScriptGenerationError(String),
709    #[error("Package installation error: {0}")]
710    PackageInstallationError(String),
711    #[error("Package removal error: {0}")]
712    PackageRemovalError(String),
713}
714
715impl Default for RIntegration {
716    fn default() -> Self {
717        Self::new().unwrap_or_else(|_| Self {
718            r_home: None,
719            library_paths: Vec::new(),
720            loaded_packages: Vec::new(),
721            workspace_variables: HashMap::new(),
722        })
723    }
724}
725
726impl Default for RScriptBuilder {
727    fn default() -> Self {
728        Self::new()
729    }
730}
731
732impl Default for RPackageManager {
733    fn default() -> Self {
734        Self::new()
735    }
736}
737
738#[allow(non_snake_case)]
739#[cfg(test)]
740mod tests {
741    use super::*;
742
743    #[test]
744    fn test_r_script_builder() {
745        let mut builder = RScriptBuilder::new();
746
747        builder
748            .require_package("stats")
749            .assign_variable("x", RValue::DoubleVector(vec![1.0, 2.0, 3.0]))
750            .add_comment("Calculate mean")
751            .add_line("mean_x <- mean(x)")
752            .add_line("cat(mean_x)");
753
754        let script = builder.build().expect("operation should succeed");
755        assert!(script.contains("library(stats)"));
756        assert!(script.contains("x <- c(1, 2, 3)"));
757        assert!(script.contains("# Calculate mean"));
758        assert!(script.contains("mean_x <- mean(x)"));
759    }
760
761    #[test]
762    fn test_r_value_conversions() {
763        let r = RIntegration::default();
764
765        // Test array to R vector
766        let data = vec![1.0, 2.0, 3.0, 4.0];
767        let r_vector = r.array_to_r_vector(&data);
768
769        match r_vector {
770            RValue::DoubleVector(ref vec) => assert_eq!(vec, &data),
771            _ => panic!("Expected DoubleVector"),
772        }
773
774        // Test R vector to array
775        let array = r
776            .r_vector_to_array(&r_vector)
777            .expect("operation should succeed");
778        assert_eq!(array, data);
779    }
780
781    #[test]
782    fn test_dataframe_creation() {
783        let r = RIntegration::default();
784
785        let mut columns = HashMap::new();
786        columns.insert("x".to_string(), RValue::DoubleVector(vec![1.0, 2.0, 3.0]));
787        columns.insert("y".to_string(), RValue::DoubleVector(vec![4.0, 5.0, 6.0]));
788
789        let df = r
790            .create_dataframe(columns)
791            .expect("operation should succeed");
792        assert_eq!(df.nrows, 3);
793        assert_eq!(df.column_names.len(), 2);
794    }
795
796    #[test]
797    fn test_r_code_generation() {
798        let r = RIntegration::default();
799
800        // Test various R value types
801        assert_eq!(
802            r.r_value_to_r_code(&RValue::Double(std::f64::consts::PI))
803                .expect("operation should succeed"),
804            format!("{}", std::f64::consts::PI)
805        );
806        assert_eq!(
807            r.r_value_to_r_code(&RValue::Integer(42))
808                .expect("operation should succeed"),
809            "42L"
810        );
811        assert_eq!(
812            r.r_value_to_r_code(&RValue::Logical(true))
813                .expect("operation should succeed"),
814            "TRUE"
815        );
816        assert_eq!(
817            r.r_value_to_r_code(&RValue::Character("test".to_string()))
818                .expect("operation should succeed"),
819            "\"test\""
820        );
821
822        let vec_result = r
823            .r_value_to_r_code(&RValue::DoubleVector(vec![1.0, 2.0, 3.0]))
824            .expect("operation should succeed");
825        assert_eq!(vec_result, "c(1, 2, 3)");
826
827        let matrix_result = r
828            .r_value_to_r_code(&RValue::Matrix {
829                data: vec![1.0, 2.0, 3.0, 4.0],
830                nrows: 2,
831                ncols: 2,
832            })
833            .expect("operation should succeed");
834        assert_eq!(matrix_result, "matrix(c(1, 2, 3, 4), nrow=2, ncol=2)");
835    }
836
837    #[test]
838    fn test_package_manager() {
839        let mut manager = RPackageManager::new();
840
841        // Test initial state
842        assert_eq!(manager.get_installed_packages().len(), 0);
843        assert!(!manager.is_installed("ggplot2"));
844
845        // Test adding packages manually (for testing)
846        manager.installed_packages.push("base".to_string());
847        manager.installed_packages.push("stats".to_string());
848
849        assert!(manager.is_installed("base"));
850        assert!(manager.is_installed("stats"));
851        assert!(!manager.is_installed("ggplot2"));
852    }
853
854    #[test]
855    fn test_workspace_variables() {
856        let mut r = RIntegration::default();
857
858        let value = RValue::Double(42.0);
859        r.save_variable("test_var", value.clone());
860
861        let retrieved = r.get_variable("test_var");
862        assert!(retrieved.is_some());
863
864        match retrieved.expect("operation should succeed") {
865            RValue::Double(val) => assert_eq!(*val, 42.0),
866            _ => panic!("Expected Double value"),
867        }
868
869        r.clear_workspace();
870        assert!(r.get_variable("test_var").is_none());
871    }
872
873    #[test]
874    fn test_output_parsing() {
875        let r = RIntegration::default();
876
877        // Test parsing different output types
878        let result = r.parse_r_output("42.5").expect("operation should succeed");
879        match result {
880            RValue::Double(val) => assert_eq!(val, 42.5),
881            _ => panic!("Expected Double"),
882        }
883
884        let result = r
885            .parse_r_output("1,2,3,4")
886            .expect("operation should succeed");
887        match result {
888            RValue::DoubleVector(vec) => assert_eq!(vec, vec![1.0, 2.0, 3.0, 4.0]),
889            _ => panic!("Expected DoubleVector"),
890        }
891
892        let result = r.parse_r_output("TRUE").expect("operation should succeed");
893        match result {
894            RValue::Logical(val) => assert!(val),
895            _ => panic!("Expected Logical"),
896        }
897
898        let result = r
899            .parse_r_output("test string")
900            .expect("operation should succeed");
901        match result {
902            RValue::Character(val) => assert_eq!(val, "test string"),
903            _ => panic!("Expected Character"),
904        }
905    }
906
907    #[test]
908    fn test_matrix_operations() {
909        let r = RIntegration::default();
910
911        let matrix = r.matrix_to_r_matrix(&[1.0, 2.0, 3.0, 4.0], 2, 2);
912
913        match matrix {
914            RValue::Matrix { data, nrows, ncols } => {
915                assert_eq!(data, vec![1.0, 2.0, 3.0, 4.0]);
916                assert_eq!(nrows, 2);
917                assert_eq!(ncols, 2);
918            }
919            _ => panic!("Expected Matrix"),
920        }
921    }
922
923    // Note: The following tests would require R to be installed and available
924    // They are commented out but show how integration testing would work
925
926    /*
927    #[test]
928    fn test_r_integration_with_real_r() {
929        let mut r = RIntegration::new().expect("operation should succeed");
930
931        // Test simple calculation
932        let result = r.execute_script("cat(2 + 2)").expect("operation should succeed");
933        assert_eq!(result.trim(), "4");
934
935        // Test statistical function
936        let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
937        let mean_result = RStatisticalFunctions::mean(&mut r, &data).expect("operation should succeed");
938        assert_eq!(mean_result, 3.0);
939    }
940
941    #[test]
942    fn test_package_operations() {
943        let mut r = RIntegration::new().expect("operation should succeed");
944
945        // Test loading base package
946        assert!(r.load_package("stats").is_ok());
947        assert!(r.get_loaded_packages().contains(&"stats".to_string()));
948
949        // Test package availability check
950        let is_available = r.is_package_available("stats").expect("operation should succeed");
951        assert!(is_available);
952    }
953    */
954}