Saltar al contenido principal
LibreTexts Español

3.11: Objetos y Clases en R

  • Page ID
    55127
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    En los capítulos que cubren Python, pasamos bastante tiempo discutiendo objetos y sus planos, conocidos como clases. En términos generales, un objeto es una colección de datos junto con funciones (llamadas “métodos” en este contexto) diseñadas específicamente para trabajar en esos datos. Las clases comprenden las definiciones de esos métodos y datos.

    Resulta que, si bien las funciones son el foco principal en R, los objetos también son una parte importante del lenguaje. (De ninguna manera ninguno de estos conceptos son mutuamente excluyentes.) Si bien las definiciones de clase están muy bien encapsuladas en Python, en R, las piezas están más distribuidas, al menos para el sistema “S3” más antiguo y más comúnmente utilizado lo discutiremos aquí. [1] Teniendo esto en cuenta, lo mejor es examinar algunos objetos y métodos existentes antes de intentar diseñar los nuestros propios. Consideremos la creación de un modelo pequeño y lineal para algunos datos de muestra.

    III.11_1_R_235_Pequeño_LM_ANOVA

    En el capítulo 30, “Listas y atributos”, aprendimos que funciones como lm () y anova () generalmente devuelven una lista (o un marco de datos, que es un tipo de lista), y podemos inspeccionar la estructura con str ().

    III.11_2_R_236_Pequeña_lm_Anova_str

    Aquí hay un muestreo de las líneas de salida para cada llamada (hay bastantes piezas de datos contenidas en la lista lm_result):

    III.11_3_R_237_pequefico_lm_anova_str_out

    Si estos dos resultados son tan similares, ambos tipos de listas, entonces, ¿por qué las salidas son tan diferentes cuando llamamos a print (lm_result)

    III.11_4_R_238_Print_LM

    e imprimir (anova_result)?

    III.11_5_R_239_Print_ANOVA

    La forma en que se producen estas impresiones viene dictada por el atributo “class” de estas listas, “lm” para lm_result y “anova” para anova_result. Si tuviéramos que eliminar este atributo, obtendríamos una salida impresa predeterminada similar al resultado de str (). Hay varias formas de modificar o eliminar el atributo class de un dato: usando la función de acceso attr () con attr (lm_result, “class”) <- NULL, configurándola usando el accessor class () más preferido, como en class (lm_result) <- NULL, o usando la función unclass () aún más especializada, como en lm_result <- unclass (lm_result). En cualquier caso, ejecutar print (lm_result) después de una de estas tres opciones dará como resultado str () como impresión predeterminada.

    Ahora bien, ¿cómo produce R una salida diferente en función de este atributo de clase? Cuando llamamos print (lm_result), el intérprete nota que el atributo “class” está establecido en “lm”, y busca otra función con un nombre diferente para ejecutar realmente: print.lm (). Del mismo modo, print (anova_result) llama a print.anova () sobre la base de la clase de la entrada. Estas funciones especializadas asumen que la lista de entrada tendrá ciertos elementos y producirá una salida específica para esos datos. Podemos ver esto tratando de confundir R estableciendo el atributo class incorrectamente con class (anova_result) <- “lm” y luego print (anova_result):

    III.11_6_R_240_Print_Anova_Clase Broken

    Observe que los nombres de clase forman parte de los nombres de las funciones. Esta es la forma de R de crear métodos, afirmando que los objetos con clase “x” deben imprimirse con print.x (); esto se conoce como despacho y la función general print () se conoce como función genérica, cuyo propósito es despachar a un método apropiado (función específica de clase) basado en el atributo class de la entrada.

    En resumen, cuando llamamos print (result) en R, porque print () es una función genérica, el intérprete comprueba el atributo “class” de result; supongamos que la clase es “x”. Si existe un print.x (), se llamará a esa función; de lo contrario, la impresión volverá a print.default (), que produce una salida similar a str ().

    III.11_7_Despedido_flujo

    Hay muchas “impresiones” diferentes. métodos; podemos verlos con métodos (“print”).

    III.11_8_R_241_Print_Methods

    De igual manera, hay una variedad de métodos “.lm” especializados en tratar datos que tienen un atributo de “clase” de “lm”. Podemos verlas con métodos (class = “lm”).

    III.11_9_R_242_Print_Methods

    El mensaje sobre las funciones no visibles que están siendo asteriscadas indica que, si bien estas funciones existen, no podemos llamarlas directamente como en print.lm (lm_result); debemos usar el genérico print (). Muchas funciones que hemos tratado son en realidad genéricos, incluyendo length (), mean (), hist () e incluso str ().

    Entonces, a su manera R, también está bastante “orientado a objetos”. Una lista (u otro tipo, como un vector o un marco de datos) con un atributo de clase dado constituye un objeto, y los diversos métodos especializados forman parte de la definición de clase.

    Creando nuestras propias clases

    Crear nuevos tipos de objetos y métodos no es algo que los programadores principiantes de R probablemente hagan a menudo. Aún así, un ejemplo descubrirá más del funcionamiento interno de R y bien podría ser útil.

    Primero, necesitaremos algún tipo de datos que queramos representar con un objeto. Para fines ilustrativos, utilizaremos los datos devueltos por la función nrorm_trunc () definida en el capítulo 35, “Programación estructural”. En lugar de producir un vector de muestras, también podríamos querer almacenar con ese vector la media del muestreo original y la desviación estándar (porque los datos truncados tendrán una media real y una desviación estándar diferentes). También podríamos desear almacenar en este objeto los límites superior e inferior solicitados. Debido a que todos estos datos son de diferentes tipos, tiene sentido almacenarlos en una lista.

    III.11_10_R_243_TRUNC_SAMPLE_CONSTRUCTOR

    La función anterior devuelve una lista con los diversos elementos, incluyendo la propia muestra. También establece el atributo class de la lista en truncated_normal_sample —by convention, este atributo class es el mismo que el nombre de la función. Tal función que crea y devuelve un objeto con una clase definida se llama constructor.

    Ahora, podemos crear una instancia de un objeto “truncated_normal_sample” e imprimirlo.

    III.11_11_R_244_TRUNC_NORM_PRINT

    Debido a que no hay ninguna función print.truncated_normal_sample (), sin embargo, el genérico print () despacha a print.default (), y la salida no es agradable.

    III.11_12_R_245_TRUNC_NORM_PRINT_OUT

    Si queremos estilizar la impresión, necesitamos crear el método personalizado. También podríamos querer crear una función mean () personalizada que devuelva la media de la muestra almacenada.

    III.11_13_R_246_TRUNC_NORM_METODOS

    La salida:

    III.11_14_R_247_TRUNC_NORM_METODOS_OUT

    Esta función de impresión personalizada es bastante cruda; técnicas de impresión más sofisticadas (como cat () y paste ()) podrían usarse para producir una salida más amigable.

    Hasta ahora, hemos definido un método personalizado mean.truncated_normal_sample (), que devuelve la media de la muestra cuando llamamos a la función genérica mean (). Esto funciona porque la función genérica mean () ya existe en R. ¿Y si quisiéramos llamar a un genérico llamado originalmean (), que devuelve original_mean del objeto? En este caso, necesitamos crear nuestro propio método especializado así como la función genérica que despacha a ese método. Así es como se ve eso:

    III.11_15_R_248_originalmean

    Estas funciones, el constructor, los métodos especializados y las funciones genéricas que aún no existen en R, necesitan definirse solo una vez, pero se pueden llamar tantas veces como queramos. De hecho, los paquetes en R que se instalan usando install.packages () a menudo son solo una colección de funciones, junto con documentación y otros materiales.

    La programación orientada a objetos es un tema grande, y solo hemos rayado la superficie. En particular, no hemos cubierto temas como el polimorfismo, donde un objeto puede tener múltiples clases listadas en el atributo “class”. En R, el tema del polimorfismo no es difícil de describir en un sentido técnico, aunque hacer un uso efectivo del mismo es un desafío en la ingeniería de software. Si un objeto tiene varias clases, como “anova” y “data.frame”, y se llama a un genérico como print (), el intérprete primero buscará print.anova (), y si eso falla, intentará print.data.frame (), y en su defecto retrocederá en print.default (). Esto permite a los objetos capturar “es un tipo de” relaciones, por lo que los métodos que funcionan con marcos de datos no tienen que ser reescritos para los objetos de clase anova.

    Ejercicios

    1. Muchas funciones en R son genéricas, incluyendo (como exploraremos en el capítulo 37, “Ploting Data y ggplot2”) la función plot (), que produce salida gráfica. ¿Cuáles son todas las diferentes clases que se pueden trazar con la parcela genérica ()? Un ejemplo es plot.lm (); use help (“plot.lm”) para determinar qué se traza cuando se le da una entrada con el atributo class de “lm”.
    2. ¿Qué métodos están disponibles para los datos con un atributo de clase de “matriz”? (Por ejemplo, ¿hay un plot.matrix () o lm.matrix ()? ¿Qué otros hay?)
    3. Crea tu propia clase de algún tipo, completa con un constructor que devuelve una lista con su conjunto de atributos de clase, un método especializado para print () y un nuevo método genérico y asociado.
    4. Explore usando otros recursos la diferencia entre el sistema de objetos S3 de R y su sistema de objetos S4.

    1. Las versiones modernas de R no tienen uno, no dos, sino tres sistemas diferentes para crear y trabajar con objetos. Estaremos discutiendo solo el más antiguo y aún más usado, conocido como S3. Los otros dos se llaman S4 y Clases de Referencia, esta última de las cuales es más similar al sistema clase/objeto utilizado por Python. Para obtener más información sobre estos y otros sistemas de objetos (y muchos otros temas avanzados de R), consulte Norman Matloff, The Art of R Programming (San Francisco: No Starch Press, 2011), y Hadley Wickham, Advanced R (Londres: Chapman y Hall/CRC, 2014).

    This page titled 3.11: Objetos y Clases en R is shared under a CC BY-NC-SA license and was authored, remixed, and/or curated by Shawn T. O’Neil (OSU Press) .