Saltar al contenido principal
LibreTexts Español

3.4: Funciones R

  • Page ID
    55148
  • \( \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}}\)

    Si bien podríamos seguir cubriendo la sintaxis vectorial única y poderosa de R, es hora de divertirnos y aprender sobre las funciones. Las funciones en R son similares a sus contrapartes Python: encapsulan un bloque de código, haciéndolo reutilizable además de permitirnos considerar el bloque aisladamente del resto del programa. Como ocurre con las funciones en la mayoría de los idiomas, las funciones R constan de tres partes principales:

    1. La entrada (parámetros dados a la función).
    2. El bloque de código que se va a ejecutar usando esos parámetros. En R, los bloques se definen por un par de corchetes coincidentes, {y}.
    3. La salida de la función, llamada el valor de retorno. Esto puede ser opcional si la función “hace algo” (como print ()) en lugar de “devuelve algo”.

    Consideremos el problema de determinar qué elementos de dos vectores numéricos, digamos vec1 y vec2, están lo suficientemente cerca como para igualarlos como para llamarlos iguales. Como se menciona en el capítulo 27, “Variables y Datos”, la forma estándar de verificar si todos los elementos en dos vectores de igual longitud son aproximadamente iguales por pares es usar iSue (all.equal (vec1, vec2)), que devuelve un solo VERDADERO si este es el caso y un solo FALSO si no.

    III.4_1_R_71_TODAS LAS FUNCIONES_EQUAL_

    Pero tal vez preferimos un vector lógico que indique qué elementos son aproximadamente iguales. La forma más sencilla de hacerlo es comparando la diferencia absoluta entre los elementos con algún pequeño valor épsilon.

    III.4_2_R_72_Equalish_Pre

    Como revisión del último capítulo, lo que está sucediendo aquí es que la -operación se vectoriza sobre los lados izquierdo- y derecho, produciendo un vector (usando reciclaje vectorial si uno de los dos fuera más corto, lo cual no es el caso aquí; ver capítulo 28), como es la función abs (), que lleva un vector y devuelve un vector de valores absolutos. De igual manera, el operador < se vectoriza, y debido a que épsilon es un vector de longitud uno, por lo que se compara con todos los elementos del resultado de abs (vec1 - vec2) utilizando reciclaje de vectores, para el resultado final de un vector lógico.

    Debido a que este tipo de operación es algo que podríamos querer realizar muchas veces, podríamos escribir una función para ello. En este caso, llamaremos a nuestra función equalish (); aquí está el código R para definir y ejecutar dicha función.

    III.4_3_R_72_ecualish_función_equalish_

    Aquí hay muchas cosas a tener en cuenta. Primero, al definir una función, definimos los parámetros que puede tomar. Los parámetros en las funciones R tienen una posición (a está en la posición 1, b está en la posición 2 y épsilon está en la posición 3) y un nombre (a, b y épsilon). Algunos parámetros pueden tener un valor predeterminado: el valor que deberían tener si no se especifica lo contrario, mientras que otros parámetros pueden ser requeridos: el usuario de la función debe especificarlos. Los valores por defecto se asignan dentro de la lista de parámetros con = (no <- como en la asignación de variables estándar).

    El bloque que define las operaciones realizadas por la función está encerrado entre corchetes, generalmente con el corchete de apertura en la misma línea que la definición de la lista de funciones/parámetros, y el corchete de cierre en su propia línea. Hemos sangrado las líneas que pertenecen al bloque de función por dos espacios (una convención R). Aunque no se requiere, esta es una buena idea, ya que hace que el código sea mucho más legible. El valor que devuelve la función se especifica con una llamada a una función return () especial: las funciones solo pueden devolver un valor, aunque podría ser algo sofisticado como un vector o un marco de datos. [1]

    Después de que se haya definido una función, se le puede llamar, como en eq <- equalish (vec1, vec2). Los nombres de variables asociados con los datos fuera de la función (en este caso vec1 y vec2) no necesitan coincidir con los nombres de los parámetros dentro de la función (a y b). Este es un punto importante al que volveremos.

    En la llamada anterior, dejamos que el parámetro epsilon tome su valor predeterminado de 0.00001. Alternativamente, podríamos usar una comparación más estricta.

    III.4_4_R_73_ecualish_función_estricta

    En R, los argumentos a las funciones pueden especificarse por posición (como en el ejemplo anterior), por nombre, o por una combinación.

    III.4_5_R_74_args_por_nombre

    Muchas funciones R toman algunos parámetros requeridos y muchos parámetros no requeridos con valores predeterminados razonables; este esquema de llamadas nos permite especificar los parámetros requeridos así como solo aquellos no requeridos que deseamos cambiar.

    En general, primero debe especificar parámetros por posición (si desea especificar alguno por posición), luego por nombre. Aunque las siguientes convocatorias funcionarán, son bastante confusas.

    imagen

    Frecuentemente usamos parámetros predeterminados para especificar parámetros con nombre en funciones llamadas dentro de la función que estamos definiendo. Aquí hay un ejemplo de una función que calcula la diferencia en medias de dos vectores; toma un parámetro opcional Remove_NAS que por defecto es FALSE. Si esto se especifica como VERDADERO, el parámetro na.rm en las llamadas a mean () se establece en TRUE también en el cálculo.

    III.4_7_R_75_2_Diff_mean

    Para la continuidad con otras funciones R, podría haber tenido más sentido llamar al parámetro na.rm; en este caso, modificaríamos las líneas de cálculo para que se leyeran como m1 <- mean (vec1, na.rm = na.rm). Aunque pueda parecer que el intérprete R estaría confundido por los nombres de variables duplicados, el hecho de que el parámetro mean () na.rm tenga el mismo nombre que la variable que se pasa no causará ningún problema.

    Variables y alcance

    Hagamos un experimento rápido. Dentro de nuestra función, el resultado de la variable ha sido asignado con el resultado de línea <- abs (a - b) < épsilon. Después de ejecutar la función, ¿es posible acceder a esa variable imprimiéndola?

    III.4_8_R_77_Experiment1

    ¡La impresión no funciona!

    III.4_9_R_78_experiment1_out

    Esta variable no se imprime porque, como en la mayoría de los idiomas, las variables asignadas dentro de las funciones tienen un alcance local a ese bloque de función. (El alcance de una variable es el contexto en el que se puede acceder a ella). Lo mismo ocurre con las variables de parámetros: no tendríamos más éxito con print (a), print (b) o print (epsilon) fuera de la función.

    Una de las mejores características de estas variables locales es que son independientes de cualquier variable que ya pueda existir. Por ejemplo, la función crea una variable llamada result (que ahora sabemos que es una variable local con el alcance del bloque de función). ¿Y si, fuera de nuestra función, también tuviéramos una variable de resultado utilizada para un propósito completamente diferente? ¿Sobrescribiría la función su contenido?

    III.4_10_R_79_Experiment2

    Fiel a la independencia de la variable de resultado local dentro de la función, el contenido del resultado externo no se sobrescribe.

    III.4_11_R_80_Experimento2_fuera

    Esta característica de cómo funcionan las variables dentro de las funciones puede parecer algo extraña, pero el resultado es importante: las funciones pueden estar completamente encapsuladas. Si están diseñados correctamente, su uso no puede afectar el contexto del código en el que se utilizan (la única forma en que las funciones R estándar pueden afectar al “mundo exterior” es devolver algún valor). Las funciones que tienen esta propiedad y siempre devuelven el mismo valor dadas las mismas entradas (por ejemplo, no tienen componente aleatorio) se denominan “puras”. Pueden tratarse como cajas negras abstractas y diseñarse aisladamente del código que las utilizará, y el código que las utilice puede diseñarse sin tener en cuenta las partes internas de las funciones que llama. Este tipo de diseño reduce drásticamente la carga cognitiva para el programador.

    Ahora, probemos el experimento inverso: si una variable se define fuera de la función (antes de que se llame), ¿se puede acceder a ella desde dentro de la definición de bloque de función?

    III.4_12_R_81_Experiment3

    La falta de error en la salida indica que sí, el bloque de función puede acceder a tales variables externas:

    III.4_13_R_82_Experiment3_out

    Esto significa que es posible escribir funciones que no toman parámetros y simplemente acceder a las variables externas que necesitarán para el cálculo.

    III.4_14_R_83_Experiment4

    Pero escribir tales funciones es bastante mala práctica. ¿Por qué? Porque aunque la función todavía no puede afectar al entorno externo, ahora depende bastante del estado del entorno externo en el que se le llama. La función solo funcionará si existen variables externas llamadas vec1, vec2 y epsilon y tienen los tipos de datos correctos cuando se llama a la función. Considera esto: la versión anterior de la función podría copiarse y pegarse en un programa completamente diferente y aún así garantizarse que funcione (porque los parámetros a y b son variables locales requeridas), pero ese no es el caso aquí.

    Las mismas cuatro “reglas” para diseñar funciones en Python se aplican a R:

    1. Las funciones solo deben acceder a variables locales que hayan sido asignadas dentro del bloque de función, o que hayan sido pasadas como parámetros (ya sea requeridos o con valores predeterminados).
    2. Documentar el uso de cada función con comentarios. ¿Qué parámetros se toman y qué tipos deberían ser? ¿Es necesario que los parámetros se ajusten a alguna especificación o hay alguna advertencia sobre el uso de la función? Además, ¿qué se devuelve?
    3. Las funciones no deberían ser “demasiado largas”. Esto es subjetivo y dependiente del contexto, pero la mayoría de los programadores se sienten incómodos con funciones que tienen más de una página de largo en su ventana de editor. La idea es que una función encapsula una idea única, pequeña y reutilizable. Si te encuentras escribiendo una función que es difícil de leer y entender, considera dividirla en dos funciones que necesitan ser llamadas en secuencia, o una función corta que llame a otra función corta.
    4. ¡Escribe muchas funciones! Incluso si un bloque de código solo va a ser llamado una vez, está bien hacer una función de él (si encapsula alguna idea o bloque bien separable). Después de todo, nunca se sabe si podría necesitar volver a usarlo, y solo el acto de encapsular el código te ayuda a asegurar que sea correcto y olvidarte de él cuando trabajes en el resto de tu programa.

    Paso de argumentos y semántica de variables

    Hasta ahora, las diferencias que hemos visto entre Python y R han estado mayormente en el énfasis de R en las operaciones vectorizadas. En capítulos posteriores, también veremos que R enfatiza el uso creativo de las funciones con más fuerza que Python (lo que al menos debería ser una buena razón para estudiarlas bien).

    Hay otra diferencia dramática entre estos dos lenguajes, que tiene que ver con las variables y su relación con los datos. Esto es probablemente más fácil de ver con un par de ejemplos de código similares. Primero, aquí hay un código Python que declara una lista de números nums, crea una nueva variable basada en la original llamada numsb, modifica el primer elemento de numsb, y luego imprime ambos.

    III.4_15_R_83_2_py_nums

    La salida indica que nums y numsb son variables (o “nombres”, en el lenguaje Python) para los mismos datos subyacentes.

    III.4_16_R_83_3_py_nums_out

    El código R correspondiente y la salida revelan que R maneja variables de manera muy diferente:

    III.4_17_R_83_4_R_nums
    III.4_18_R_83_5_R_nums_out

    Mientras que en Python es común que los mismos datos subyacentes sean referenciados por múltiples variables, en R, las variables únicas casi siempre están asociadas con datos únicos. A menudo, estas semánticas se enfatizan en el contexto de variables locales para funciones. Aquí está lo mismo, pero la operación está mediada por una llamada de función. Primero, la versión y salida de Python:

    III.4_19_R_83_6_Py_Func
    III.4_20_R_83_7_Py_Func_out

    Y ahora la versión R y salida:

    III.4_21_R_83_8_R_Func
    III.4_22_R_83_9_R_Func_out

    En el código Python, la variable local param es una nueva variable para los mismos datos subyacentes, mientras que en el código R la variable param local es una nueva variable para nuevos datos subyacentes. Estos dos paradigmas se encuentran en una amplia variedad de lenguajes; este último se conoce como “pass-by-value”, aunque uno podría pensarlo como “pass-by-copy”. Esto no significa que R siempre cree una copia, usa una estrategia de “copiar sobre escritura” detrás de escena para evitar el exceso de trabajo. En cuanto a la primera, la documentación de Python se refiere a ella como “pass-by-assignment”, y el efecto es similar a “pass-by-reference”. (El término “pass-by-reference” tiene una definición técnica muy estrecha, pero a menudo se usa como un “catch-all” para este tipo de comportamiento).

    Hay ventajas e inconvenientes en ambas estrategias. El esquema algo más difícil utilizado por Python es a la vez más rápido y permite implementaciones más fáciles de algunos algoritmos sofisticados (como las estructuras cubiertas en el capítulo 25, “Algoritmos y estructuras de datos”). El esquema de paso por valor, por otro lado, puede ser más fácil de codificar, porque las funciones que siguen la regla 1 anterior no pueden modificar subrepticiamente los datos: están “libres de efectos secundarios”.

    Obteniendo Ayuda

    El intérprete R viene con una amplia documentación para todas las funciones que están incorporadas. Ahora que sabemos escribir funciones, leer esta documentación será fácil.

    Se puede acceder a la página de ayuda para una función en particular ejecutando help (“function_name”) en la consola interactiva, como en help (“t.test”) para obtener ayuda con la función t.test ().

    III.4_23_R_84_Help1

    Alternativamente, si el nombre de la función no contiene caracteres especiales, podemos usar la taquigrafía? nombre_función, como en? t.test. La ayuda se proporciona en una ventana interactiva en la que puedes usar las teclas de flecha para desplazarte hacia arriba y hacia abajo.

    III.4_24_R_85_Help1_out

    Las páginas de ayuda generalmente tienen las siguientes secciones, y puede haber otras:

    • Descripción: Breve descripción de la función.
    • Uso: Un ejemplo de cómo se debe llamar a la función, generalmente listando los parámetros más importantes; los parámetros con valores predeterminados se muestran con un signo igual.
    • Argumentos: Lo que hace cada parámetro aceptado por la función.
    • Detalles: Notas sobre la función y advertencias a tener en cuenta al usar la función.
    • Valor: Lo que devuelve la función.
    • Referencias: Cualquier artículo de revista pertinente o citas de libros. Estos son particularmente útiles para funciones estadísticas complejas.
    • Ejemplos: Código de ejemplo usando la función. Desafortunadamente, se escriben muchos ejemplos para aquellos que están familiarizados con los conceptos básicos de la función, e ilustran un uso más complejo.
    • Ver también: Otras funciones relacionadas que un usuario puede encontrar interesantes.

    Si una función pertenece a un paquete (como str_split () en el paquete stringr), se puede cargar primero el paquete (con library (stringr)) y acceder a la ayuda de la función como de costumbre (help (“str_split”)), o especificar el paquete directamente, como en help (“str_split”, package = “stringr”). Se puede acceder a una página de ayuda general para todas las funciones del paquete con ayuda (package = “stringr”).

    Por último, en la ventana interactiva, utilizando help.search (“average”) buscará en la documentación todas las funciones mencionando el término “promedio” —el atajo para esto es?? promedio.

    Ejercicios

    1. A menudo deseamos “normalizar” un vector de números restando primero la media de cada número y luego dividiendo cada uno por la desviación estándar de los números. Escribe una función llamada normalize_mean_sd () que tome dicho vector y devuelva la versión normalizada. La función debería funcionar incluso si algún valor es NA (la versión normalizada de NA debería ser simplemente NA).
    2. La función t.test () prueba si las medias de dos vectores numéricos son desiguales. Existen múltiples versiones de t -tests: algunos asumen que las varianzas de los vectores de entrada son iguales, y otras no hacen esta suposición. Por defecto, ¿t.test () asume varianzas iguales? ¿Cómo se puede cambiar este comportamiento?
    3. Usando la documentación de ayuda, genere un vector de 100 muestras a partir de una distribución de Poisson con el parámetro lambda (que controla la forma de la distribución) establecido en 2.0.
    4. La siguiente función calcula la diferencia en la media de dos vectores, pero rompe al menos una de las “reglas” para escribir funciones. Arreglarlo para que se ajuste. (Tenga en cuenta que también le falta la documentación adecuada.) III.4_25_R_85_2_FIX_FUNC
    5. El siguiente código genera dos muestras aleatorias, y luego calcula e imprime la diferencia en el coeficiente de variación para las muestras (definida como la desviación estándar dividida por la media). Explique cómo funciona este código, paso a paso, en términos de variables locales, parámetros y valores devueltos. Y si inmediatamente antes de la muestra1 <- rnorm (100, media = 4, sd = 2), tuviéramos resultado <- “Mensaje de prueba. ", y después de print (answer), teníamos print (result)? ¿Qué se imprimiría y por qué? III.4_26_R_85_3_Explain_Func

    1. Cualquier variable que se indique simplemente (sin asignación) en una función será devuelta. Entonces, esta definición es equivalente:III.4_27_R_9000_ecualish_función_no_retorno Algunos programadores de R prefieren esta sintaxis; para este texto, sin embargo, nos apegaremos a usar el return () más explícito. Esto también ayuda a diferenciar entre tales “devoluciones sin retorno” y “impresiones sin impresión” (ver la nota al pie en el Capítulo 27, Variables y Datos).

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