1081 lines (1080 with data), 39.0 kB
{
"cells": [
{
"cell_type": "markdown",
"id": "f63519ce-ca5a-4f64-93a5-c5491b58d46b",
"metadata": {},
"source": [
"<img src=\"images/RIINBRE-Logo.jpg\" width=\"400\" height=\"400\"><img src=\"images/MIC_Logo.png\" width=\"600\" height=\"600\">"
]
},
{
"cell_type": "markdown",
"id": "fc55d591-83ea-4db3-9c26-23831e09008b",
"metadata": {},
"source": [
"# Analysis of Biomedical Data for Biomarker Discovery\n",
"## Submodule 2: Introduction to R Data Structures\n",
"### Dr. Christopher L. Hemme\n",
"### Director, [RI-INBRE Molecular Informatics Core](https://web.uri.edu/riinbre/mic/)\n",
"### The University of Rhode Island College of Pharmacy"
]
},
{
"cell_type": "markdown",
"id": "4ca07ca3-7e42-42aa-ac12-36f85cd8cd4a",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "4e23d9ba",
"metadata": {},
"source": [
"## Overview\n",
"\n",
"This Jupyter Notebook provides an introduction to R data structures, serving as Submodule 2 of a larger course on analyzing biomedical data for biomarker discovery. It covers basic R data types (numeric, integer, character, logical, NA, NaN, Inf), explaining how to check and convert between them. The notebook then focuses on core data structures: vectors, lists, matrices, arrays, and data frames. It emphasizes the concepts of dimensionality and homogeneity/heterogeneity for each structure, and provides examples of how to create and manipulate them using functions like c(), list(), matrix(), and data.frame(). It highlights best practices for handling missing data (NA) and potential pitfalls like data type coercion. The notebook also includes embedded quizzes and concludes by explaining the importance of understanding R data structures for tasks like regression analysis in bioinformatics. It's targeted towards beginners in R, specifically those interested in biomedical data analysis, and suggests users adopt tibbles (from the tidyverse) for a more modern approach to data frames."
]
},
{
"cell_type": "markdown",
"id": "a6678fe8",
"metadata": {},
"source": [
"## Learning Objectives\n",
"\n",
"+ **Understanding R Data Types:** Learn to identify and convert between different data types like numeric, integer, character, logical, and special values like NA, NaN, and Inf.\n",
"+ **Working with Missing Values (NA):** Understand the difference between NA and zero, and learn how to handle missing data in calculations using `na.rm`.\n",
"+ **Understanding R Data Structures:** Learn about the different data structures available in R (vectors, lists, matrices, arrays, and data frames) and their properties of dimensionality (1D, 2D, multi-dimensional) and homogeneity (same or different data types within the structure).\n",
"+ **Creating and Manipulating Vectors:** Learn how to create vectors using `c()`, determine their length, access elements using bracket notation, including subsetting with logical vectors and conditional statements.\n",
"+ **Creating and Manipulating Lists:** Learn how to create lists using `list()`, understand their heterogeneous nature, access elements using single and double bracket notation, and understand how to work with nested data structures within lists.\n",
"+ **Creating and Manipulating Matrices and Arrays:** Learn how to create matrices using `matrix()`, control their dimensions with `nrow`, `ncol`, and `byrow`, access elements using bracket notation, and understand the difference between `dim` and `length`.\n",
"+ **Creating and Manipulating Data Frames:** Learn how to create data frames using `data.frame()`, understand their structure in relation to spreadsheet-like data, access columns using bracket and `$` notation, convert columns to factors, and understand common data import issues and coercion.\n",
"+ **Best Practices for Data Frames:** Be introduced to the concept of tibbles as a modern alternative to data frames.\n",
"+ **Relevance to Bioinformatics and Regression Analysis:** Understand why data structures are important in bioinformatics analyses, especially in the context of regression models where specific data structures (matrices, vectors) are required for operations."
]
},
{
"cell_type": "markdown",
"id": "9fad852c-746f-4665-80ad-bf34dc68798d",
"metadata": {},
"source": [
"## Get Started"
]
},
{
"cell_type": "markdown",
"id": "1d4d3359-c8b3-4936-8165-c27aa52c7ec8",
"metadata": {},
"source": [
"Before we begin working with data, we need to discuss some fundamental topics relevant to data processing and analysis. We will start with a discussion on common data structures in R. These data structures will be important for building our experimental object and for regression analysis. This is not intended to be a comprehensive review of R. If you are already familiar with R, you can skip to Chapter 3: Linear Models."
]
},
{
"cell_type": "markdown",
"id": "3440e627-8984-4f92-9637-41bd479222c0",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-info\">\n",
"<b>✋ Tip:</b> Blue boxes will indicate helpful tips.</div>"
]
},
{
"cell_type": "markdown",
"id": "de503954-faa3-4caf-a187-ade2d6ac46c7",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-warning\">\n",
"<b>🎓 Note:</b> Used for interesting asides or notes.\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "d37299fd-ac17-44b8-8af3-64ab55d55559",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-success\">\n",
"<b>✍ Reference:</b> This box indicates a reference for an attached figure or table.\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "59fff243-9787-4690-8924-ecf71b2c7fa9",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-danger\">\n",
"<b>🛑 Caution:</b> A red box indicates potential hazards or pitfalls you may encounter.\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "b1a533d6-5fa4-48f2-bae3-23220b03d75f",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "0f13e0f2-9e3e-4fad-b1bb-e435f01c779b",
"metadata": {},
"source": [
"## Data Structures in R"
]
},
{
"cell_type": "markdown",
"id": "1d7410fd-445d-4411-8b90-6cf91e0527c8",
"metadata": {},
"source": [
"When working with data in R, we need to know two things: what type of data are we working with, and what data structure should be used? Data types in R include **numeric** (floating point numbers), **integer**, **character** (text), and **logical** (TRUE/FALSE). There are several ways to determine the type of your data. The first is with the *class* function:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "97a3f878-e477-4db1-99ee-0031edd6ba1b",
"metadata": {},
"outputs": [],
"source": [
"vec <- c(1:10)\n",
"vec\n",
"class(vec)"
]
},
{
"cell_type": "markdown",
"id": "1725d2eb-f04d-4b3e-8a8b-5e92bb90f5c2",
"metadata": {},
"source": [
"Here we create a vector of integers from 1 to 10 (we'll discuss what a vector is in a moment). The __*class*__ function tells us that this data is of type **integer**.\n",
"\n",
"A second way to determine the data type is with the __*str*__ function:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "70b6d800-3c9e-4349-bb39-4f8e671c386e",
"metadata": {},
"outputs": [],
"source": [
"str(vec)"
]
},
{
"cell_type": "markdown",
"id": "0f2f78a6-61a8-4cc2-a6d8-e9bfe9eb8943",
"metadata": {},
"source": [
"__*str*__ gives us the structure of the data. In this case, it's a simple vector of integers. For more complex data structures, __*str*__ is a valuable tool for understanding what types of data are in your data structure and how it is organized. For now, let's look at the third way to identify the data type. Most data types in R have a corresponding __*is*__ function that indicates using logical values whether the data is or is not of a specific type."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "58c101e6-ecdd-401e-a7b0-fe15daf96f02",
"metadata": {},
"outputs": [],
"source": [
"is.integer(vec)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8cf9cf5b-bb7e-4504-bcf0-a72863d7b064",
"metadata": {},
"outputs": [],
"source": [
"is.numeric(vec)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "53c72e72-34d1-4474-825a-015d40414be9",
"metadata": {},
"outputs": [],
"source": [
"is.character(vec)"
]
},
{
"cell_type": "markdown",
"id": "e451ef17-81aa-413b-a073-71f9478d47b4",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-info\">\n",
"<b>✋ Tip:</b> Notice that vec is both integer and numeric. Integer values are numeric, but numeric values are not necessarily integers.</div>"
]
},
{
"cell_type": "markdown",
"id": "b4c6d9bb-b69a-4de0-9fa0-e46b0d5716ec",
"metadata": {},
"source": [
"For converting between data types, each type has a corresponding *as* function:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d1007278-63c5-4620-9a01-05b6cd908865",
"metadata": {},
"outputs": [],
"source": [
"is.integer(vec)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "16728333-eb97-45ca-a617-c9518977131b",
"metadata": {},
"outputs": [],
"source": [
"vec_char <- as.character(vec)\n",
"is.integer(vec_char)\n",
"is.character(vec_char)"
]
},
{
"cell_type": "markdown",
"id": "146b8112-80d1-44f2-828e-ec3f4240a35b",
"metadata": {},
"source": [
"There are a few special data types we should cover: **NA**, **NAN** and **+/-Inf**.\n",
"\n",
"**NA** means 'not available' and is the most common method of displaying missing data."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "fbcd752e-5c96-482a-825b-195c56c99710",
"metadata": {},
"outputs": [],
"source": [
"vec_zero <- c(1,3,4,0,7,9)\n",
"vec_zero"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b1140914-a37d-46ec-afcb-43285e9e768f",
"metadata": {},
"outputs": [],
"source": [
"vec_na <- c(1,3,4,NA,7,9)\n",
"vec_na"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "63fa169a-75ed-4218-a14a-581b53cc3d39",
"metadata": {},
"outputs": [],
"source": [
"mean(vec_zero)\n",
"mean(vec_na)\n",
"mean(vec_na, na.rm = TRUE)"
]
},
{
"cell_type": "markdown",
"id": "13e0d4e0-c880-4d3e-ab28-f9026660fdb2",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-danger\">\n",
"<b>🛑 Caution:</b> Notice that NA is not the same as zero. In vec_zero, the mean function calculates the mean based on six values including zero, returning a mean of 4. When we replace the zero with NA, mean returns NA unless we add the argument na.rm = TRUE (meaning remove NA). The mean of vec_na is then calculated based on five values, returning a mean of 4.8. When working with real data, we will almost always have to deal with missing values so it's important to know how to deal with it.\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "c1782e4c-c0d0-4b8b-a819-5c6729b41799",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-info\">\n",
"<b>✋ Tip:</b> 'na.rm' is a common argument in many R functions. Later we will show ways to work with NA values during import.</div>"
]
},
{
"cell_type": "markdown",
"id": "7d2397ba-4a8f-4e33-b93e-9e50ce8f3536",
"metadata": {},
"source": [
"Now consider the following examples:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "df84d629-f55f-49f5-a19c-d1ca22d63b78",
"metadata": {},
"outputs": [],
"source": [
"0/0\n",
"1/0\n",
"-1/0"
]
},
{
"cell_type": "markdown",
"id": "edea7b74-0ec3-4fdb-9953-dad66013091e",
"metadata": {},
"source": [
"In many programming languages, attempting to carry out an improper mathematical operation such as dividing by zero will return an error. In R, we instead get the following values:\n",
"<ul>\n",
" <li><b>NaN</b> - Not a Number\n",
" <li><b>Inf</b> - Positive Infinity\n",
" <li><b>-Inf</b> - Negative Infinity\n",
"</ul>\n",
"\n",
"This functionality allows you to work with these types of situations with requiring extra error checking routines. As with other data types, "
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "18ffae80-f9ae-4757-94dd-608552482bfb",
"metadata": {},
"outputs": [],
"source": [
"vec_nan <- c(1, 3, NA, NaN, Inf, -Inf, 10)\n",
"vec_nan"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3a94efa0-f462-456f-88c9-824612eac8a6",
"metadata": {},
"outputs": [],
"source": [
"is.na(vec_nan)\n",
"is.nan(vec_nan)\n",
"is.infinite(vec_nan)\n",
"is.finite(vec_nan)"
]
},
{
"cell_type": "markdown",
"id": "539bd7ad-b577-49ff-96f0-68e01b530616",
"metadata": {},
"source": [
"The __*is*__ functions return logical vectors indicating which elements of the vector (or other data structure) meet the specified condition."
]
},
{
"cell_type": "markdown",
"id": "c306c7e3-7eb7-41e5-ae40-dd7c1d562cdf",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-info\">\n",
"<b>✋ Tip:</b> Notice that NaN is also considered NA.</div>"
]
},
{
"cell_type": "markdown",
"id": "586ce19c-01de-4184-87c3-405ca29eacb0",
"metadata": {},
"source": [
"You can negate these operations by using the __*!*__ operator. For instance, to identify which values in *vec_nan* are NOT **NA**, use:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "435c6819-aa25-42d9-9dfb-424faa0c27b8",
"metadata": {},
"outputs": [],
"source": [
"!is.na(vec_na)"
]
},
{
"cell_type": "markdown",
"id": "4e0e7230-6e62-4da2-99e9-df8213b6dfee",
"metadata": {},
"source": [
"Logical vectors are also very useful for subsetting data structures."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6a01bd90-b2cf-4db6-b856-c373a2f45194",
"metadata": {},
"outputs": [],
"source": [
"vec_nan[is.finite(vec_nan)]\n",
"vec_nan[!is.na(vec_nan)]"
]
},
{
"cell_type": "markdown",
"id": "1f0784e3-c1cd-45ba-9bae-cefd95847ea4",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-info\">\n",
"<b>✋ Tip:</b> Even though NA, NaN and Inf look like character data types, R recognizes them as special cases and will not coerce numeric data if it sees these values.</div>"
]
},
{
"cell_type": "markdown",
"id": "9cf5dbf8-3737-4237-b7be-a6f5596d6ca6",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "b04f2ad8-8538-4bf6-b045-a29a81b43c60",
"metadata": {},
"source": [
"Now that we understand data types, let's discuss data structures. A data structure is a way to organize our data in a consistent form that can be easily manipulated by other R functions. When we store data in a variable, we must either explicitly define the data structure or trust R to define it for us. For example, when we load a text file into R, it will typically be loaded as a data frame unless we explicitly define it as, for example, a matrix.\n",
"\n",
"Data structures in R are typically defined by two properties: dimensionality and homogeneity. A homogeneous data structure means that all values in the data structure are of the same data type, e.g., all numeric. A heterogeneous data structure allows for each element to be its own type, though there may still be restrictions on data types within that element.\n",
"\n",
"Dimensionality simply means how many dimensions the data structure is composed of. A 1-dimensional data structure is an ordered sequence of values (e.g., 1,2,3,4,5). Multi-dimensional data structures add one or more dimensions to the data (e.g., a 2-dimensional data structure is similar to how data is organized in a spreadsheet). Below is a table of the most common base R data structures."
]
},
{
"cell_type": "markdown",
"id": "c46730be-1210-4cc4-9c71-cb419e38bdda",
"metadata": {},
"source": [
"<table>\n",
"<thead>\n",
" <tr><th>Dimensionality</th><th>Homogeneous</th><th>Heterogeneous</th></tr>\n",
"</thead>\n",
"<tbody>\n",
" <tr><td>1-Dimensional</td><td>Vector</td><td>List</td></tr>\n",
" <tr><td>2-Dimensional</td><td>Matrix</td><td>Data Frame</td></tr>\n",
" <tr><td>Multi-Dimensional</td><td>Array</td><td>-</td></tr>\n",
"</tbody>\n",
"</table>"
]
},
{
"cell_type": "markdown",
"id": "1eb3c31e-5235-414b-8afc-0b2769e67801",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-info\">\n",
"<b>✋ Tip:</b> These are the base R data structures, but you will often see package developers create new data structures for specific applications. These are usually based off of the existing base R data structures but allow for additional functionality, specific storage patterns, etc. Some common enhanced data structures you might see include:\n",
"<ul>\n",
" <li>Data Table - A modified data frame used in the data.table package that is optimized for large datasets and mimics the functionality of databases</li>\n",
" <li>Tibble - A modified data frame built along tidy principles and implemented in the tidyverse suite of packages</li>\n",
" </ul>\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "2bc6c678-15a8-4171-a881-b3bd64ee4c81",
"metadata": {},
"source": [
"As with data types, R data structures usually have associated is and as functions for identification and conversion."
]
},
{
"cell_type": "markdown",
"id": "10ae3412-9d5c-4105-bf4b-36d17f8c0b55",
"metadata": {},
"source": [
"### Vectors"
]
},
{
"cell_type": "markdown",
"id": "5cc29895-c71f-4bf6-a1f7-a9f403bcf2d2",
"metadata": {},
"source": [
"The vector is the fundamental data structure in R and is a 1-dimensional homogeneous sequence of values. We have already seen how to create a vector using the __c()__ function. A single value is called a scalar, but technically speaking, a scalar in R is just a vector of length 1."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "291272de-de31-48aa-9eef-82e2d9f45cf6",
"metadata": {},
"outputs": [],
"source": [
"vec"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1a9d6cd9-41af-4e2d-9ed6-f1debbd7b714",
"metadata": {},
"outputs": [],
"source": [
"length(vec)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "99fdb7ee-99d7-4d72-abe9-3362cb617775",
"metadata": {},
"outputs": [],
"source": [
"is.vector(vec)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e31ab7ce-7d83-4262-943f-6ed4fb79949f",
"metadata": {},
"outputs": [],
"source": [
"is.list(vec)"
]
},
{
"cell_type": "markdown",
"id": "64541aa9-3312-4054-854c-430adaf30f03",
"metadata": {},
"source": [
"You might have encountered vectors in previous math classes to indicate the direction and magnitude of an arrow. That's exactly what we're working with in R, just on a more generalized scale. R is specifically designed to carry out mathematical operations on vectors and matrices. In fact, R uses a process called vectorization to carry out operations on all elements of a data structure, meaning that we can carry out simple or complex mathematical operations without having to write specific looping functions."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "883b7f67-20c8-4234-9acf-3feb2e40d1c5",
"metadata": {},
"outputs": [],
"source": [
"2*vec"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "732eb509-38a7-4865-86e6-f4c8357ea7ba",
"metadata": {},
"outputs": [],
"source": [
"vec^2"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b38b9369-201c-45cc-8142-b7b04fb491ea",
"metadata": {},
"outputs": [],
"source": [
"vec * t(vec)"
]
},
{
"cell_type": "markdown",
"id": "62f794a1-f1f8-47df-8cbc-7c1156232986",
"metadata": {},
"source": [
"As previously mentioned, a vector is homogeneous, meaning all values must be of the same type. This is important to remember, because in the case of ambiguous data, R will attempt to coerce the data to a single type which can cause problems for your analysis. Consider the following cases:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "74f1f7e7-6d45-497d-8320-f0ff124119d1",
"metadata": {},
"outputs": [],
"source": [
"vec_num <- c(1,3,4,7,9,12)\n",
"class(vec_num)\n",
"mean(vec_num)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "39d62f44-262e-430e-a254-06d2f88b4e05",
"metadata": {},
"outputs": [],
"source": [
"vec_char <- c(1,3,4,\"7\",9,12)\n",
"class(vec_char)\n",
"mean(vec_char)"
]
},
{
"cell_type": "markdown",
"id": "4d8ca0cb-73fb-4cc1-b8a8-42afca0dee9d",
"metadata": {},
"source": [
"In __*vec_char*__, a single character forces every element of the vector to be changed to the character type. When we try to carry out mathematical operations on this vector, we get a warning because the mean function expects numeric data.\n",
"\n",
"This is a very common problem when importing data from external sources such as spreadsheets, so it's important to always check the structure of your data when importing to ensure that your data is what you expect it to be.\n",
"\n",
"To extract elements from a vector, we use square bracket notation. There are a variety of ways to extract specific elements from a vector, some of which are shown below."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f64c191-0167-484d-8249-12d6389b1f09",
"metadata": {},
"outputs": [],
"source": [
"vec\n",
"vec[1]\n",
"vec[-1]\n",
"vec[1:3]\n",
"vec[c(1,3)]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "458919ed-30f0-4907-b333-a6719fc5f0de",
"metadata": {},
"outputs": [],
"source": [
"vec_log <- c(TRUE, FALSE, TRUE, TRUE, TRUE, FALSE, TRUE, FALSE, FALSE, TRUE)\n",
"vec[vec_log]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1a61ff54-db86-4801-afaa-c774c928fafa",
"metadata": {},
"outputs": [],
"source": [
"vec[vec > 6]\n",
"vec[vec > 2 & vec <= 7]"
]
},
{
"cell_type": "markdown",
"id": "66494c72-1793-4a23-91ee-4ce9d43542f9",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "48607980-129b-49fd-9fa5-29a2136d826f",
"metadata": {},
"source": [
"### Lists"
]
},
{
"cell_type": "markdown",
"id": "a5ea806f-0240-4b2d-98cd-7b52d551a138",
"metadata": {},
"source": [
"A list is a heterogeneous version of a vector. In other words, each element of a list can be any data type. While this may seem chaotic compared to the cleaner vector, it allows us to group together related data that might not necessarily be of the same type. We create lists using the list function."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c17989c9-3b3c-403c-9e33-7e5018988983",
"metadata": {},
"outputs": [],
"source": [
"l <- list(\"scalar\" = 1, \"numeric\" = 1:10, \"character\" = c(\"a\", \"b\", \"c\"), \"logical\" = c(TRUE, FALSE, TRUE, FALSE))\n",
"l\n",
"str(l)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5a24ab99-0410-4594-9694-707768badca6",
"metadata": {},
"outputs": [],
"source": [
"is.list(l)\n",
"is.data.frame(l)"
]
},
{
"cell_type": "markdown",
"id": "439839ec-9c24-4021-9ec7-386da4538df0",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-info\">\n",
"<b>✋ Tip:</b> Most data structures in R allow you to name sets of data which you can then refer to instead of the numeric index. This simplifies manipulating large data sets.</div>"
]
},
{
"cell_type": "markdown",
"id": "b1371cdd-cc25-4c16-b0bd-e4a77af7d017",
"metadata": {},
"source": [
"Notice a few properties of our list. First, each element of the list is a different length. In a list, this is perfectly acceptable, but it means that it's up to you to process each element of the list properly. Second, the final three element of the list are themselves vectors. In fact, we can add any data structure to a list, including other lists. This allows us to create very complex multidimensional data structures even though a list is technically only one dimension. This is a powerful feature of lists, but again, it is up to you to understand the structure of your list and how to get the relevant data out of it.\n",
"\n",
"Using the bracket notation that we learned with vectors will return another list. This isn't always what we want. To get the data itself from a list, we use the double bracket notation. In the example below, the double bracket notation returns a vector stored in the second element of the list. We can then chain that function with standard single bracket notation to get the second element of the vector stored in the second element of the list. This chain functionality is how we can access data that is stored deeply in a complex list."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8431ffd9-0efe-4339-813b-2ca0d8034944",
"metadata": {},
"outputs": [],
"source": [
"l[2]\n",
"l[[2]]\n",
"l[[2]][2]"
]
},
{
"cell_type": "markdown",
"id": "b3d35503-5ec7-413f-bf6f-7e88c757fac4",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "c56ba7e0-5fee-4329-9da9-96491a137065",
"metadata": {},
"source": [
"### Matrices and Arrays"
]
},
{
"cell_type": "markdown",
"id": "b2f07107-2a9d-45a7-8515-14c25cd43e06",
"metadata": {},
"source": [
"An array is a multi-dimensional data structure that holds data of the same type. A vector is a one-dimensional array, and a two dimensional array is called a matrix."
]
},
{
"cell_type": "markdown",
"id": "e2a556f8-6ff3-432e-b816-1bc5ec53f787",
"metadata": {},
"source": [
"Vector (dimensions 1x4): $$\\begin{bmatrix} 6 & 4 & 3 & 7 \\end{bmatrix}$$"
]
},
{
"cell_type": "markdown",
"id": "7224eabc-f695-418b-8401-fc49e0b07c1b",
"metadata": {},
"source": [
"Matrix (dimensions 4x4): $$\\begin{bmatrix} 6 & 4 & 3 & 7 \\\\ 2 & 1 & 1 & 4 \\\\ 9 & 1 & 3 & 8 \\\\ 6 & 3 & 2 & 1 \\end{bmatrix}$$ "
]
},
{
"cell_type": "markdown",
"id": "2e601835-fd78-4e44-8894-72dc5eafe515",
"metadata": {},
"source": [
"Array (dimensions 4x4x3): $$\\begin{bmatrix} 6 & 4 & 3 & 7 \\\\ 2 & 1 & 1 & 4 \\\\ 9 & 1 & 3 & 8 \\\\ 6 & 3 & 2 & 1 \\end{bmatrix}_1$$\n",
"$$\\begin{bmatrix} 3 & 2 & 8 & 1 \\\\ 3 & 3 & 9 & 7 \\\\ 3 & 6 & 5 & 4 \\\\ 7 & 8 & 9 & 2 \\end{bmatrix}_2$$ \n",
"$$\\begin{bmatrix} 1 & 9 & 0 & 1 \\\\ 4 & 2 & 9 & 7 \\\\ 5 & 3 & 5 & 3 \\\\ 2 & 7 & 8 & 9 \\end{bmatrix}_3$$ "
]
},
{
"cell_type": "markdown",
"id": "1604e10d-60c4-4ecf-a2cb-36e9f368618b",
"metadata": {},
"source": [
"Matrices are the most common type of multidimensional array we deal with, so we'll focus on those going forward. We can build a matrix using the __*matrix*__ function."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "66c2971e-aba1-47d9-ad8e-fc1528bc7c00",
"metadata": {},
"outputs": [],
"source": [
"mat <- matrix(1:16, nrow = 4, ncol = 4)\n",
"mat\n",
"is.matrix(mat)\n",
"mat_byrow <- matrix(1:16, nrow = 4, ncol =4, byrow = TRUE)\n",
"mat_byrow\n",
"is.matrix(mat_byrow)"
]
},
{
"cell_type": "markdown",
"id": "5abc70f8-cba1-412b-886d-78c3382aa5ef",
"metadata": {},
"source": [
"The first argument to __*matrix*__ is the data itself. We use __*nrow*__ and __*ncol*__ to define the dimensions of the matrix, and the __*byrow*__ argument determines whether the matrix is populated by row or by column. We can check the dimensions of our matrix using the __*dim*__ function."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "46a5f5bf-0359-4093-9995-591bdbcc3284",
"metadata": {},
"outputs": [],
"source": [
"dim(mat)"
]
},
{
"cell_type": "markdown",
"id": "a48fb2c5-96ea-459c-86e2-412831e1a7d5",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-danger\">\n",
" <b>🛑 Caution:</b> <i>dim</i> will not work on a vector. Use <i>length</i> instead. <i>length</i> will also work on a matrix, but it will return the total number of elements in the matrix (e.g., a 4x4 matrix will have length = 16.\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "a3ef1d02-99d6-44ac-a740-1fb6db3c6308",
"metadata": {},
"source": [
"To access elements of a matrix, we use the single bracket notation of vectors but with a second dimension, with the first dimension representing rows and the second representing columns. By leaving one or the other blank, we can extract all rows or columns (just don't forget the comma)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "eedf0ddc-df30-4b15-b725-66ed6d0e3bd2",
"metadata": {},
"outputs": [],
"source": [
"mat[1,1]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c3420759-8bf4-460d-90ce-66cce6b62dbb",
"metadata": {},
"outputs": [],
"source": [
"mat[1,]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "34b01c93-9a58-4b00-9921-9494b7de4de0",
"metadata": {},
"outputs": [],
"source": [
"mat[,1]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "861808c7-3cdc-4dd5-9c86-1e3abb207a1f",
"metadata": {},
"outputs": [],
"source": [
"mat[1:3, 1:3]"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "41730616-a260-48e6-9764-f148fadc1b22",
"metadata": {},
"outputs": [],
"source": [
"mat[mat > 5]"
]
},
{
"cell_type": "markdown",
"id": "3032d56c-8529-485a-9977-12be964f4e0e",
"metadata": {},
"source": [
"We will cover matrices and matrix operations in more detail in <b>Submodule 3: Introduction to Linear Models</b>."
]
},
{
"cell_type": "markdown",
"id": "447d2622-acaa-4705-b3b4-393f83600159",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "a2b83d70-6a38-4088-a995-22004dba021d",
"metadata": {},
"source": [
"### Data Frames"
]
},
{
"cell_type": "markdown",
"id": "1dc6b08e-6b5f-43f5-a71b-5881a0e1440a",
"metadata": {},
"source": [
"A **data frame** is a two-dimensional data structure analogous to a spreadsheet. The rows of a data frame represent some feature (e.g., sample names) while the columns represent variables that define the data (e.g., Treatment state, time points, etc.). Data frames are heterogeneous in the sense that each column can be its own data type, but within a column, all data must be of the same type. For example, if you have a column \"Time Point\" that consists of numeric values, then all elements of that column will be numeric unless coerced to something else.\n",
"\n",
"The data frame is one of the most common data structures and will almost always be the default data structure when importing external data (such as a spreadsheet). One common use of data frames in bioinformatics is for phenotypic data (also called metadata), that is, data used to describe samples (as opposed to experimental data)."
]
},
{
"cell_type": "markdown",
"id": "5516bf08-67b3-4b2c-8916-79071814239a",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-danger\">\n",
" <b>🛑 Caution:</b> One of the most common problems encountered with data frames is coercion of your data because the imported data isn't sufficiently cleaned (especially when importing data from Excel). Issues include unexpected text or invisible control codes in numeric columns that coerce the data to character, or improperly defined missing data ('0' or '-' instead of blank cells or NA).\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "73af89c4-70f8-42e5-98b8-a8ade012c602",
"metadata": {},
"source": [
"<div class=\"alert alert-block alert-info\">\n",
"<b>✋ Tip:</b> While it's useful to know how to manipulate data frames in base R, we strongly recommend users learn how to use the <b>tibble</b> instead. The tibble is a modern implementation of the data frame that has most of the same functionality but removes some of the weird quirks that have plagued data frames since their inception. Tibbles are implemented in the <i>tibble</i> package which is part of the <i>tidyverse</i> suite of packages. This makes tibbles fully compatible with other <i>tidyverse</i> packages such as <i>ggplot2</i> and <i>dplyr</i> and greatly simplifies tasks such as data subsetting.</div>"
]
},
{
"cell_type": "markdown",
"id": "2fa3b863-fd54-4f78-9520-7ef28cde6d40",
"metadata": {},
"source": [
"Data frames are constructed using the __<i>data.frame</i>__ function."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "916f477c-357a-48a2-92eb-c77bb384d573",
"metadata": {},
"outputs": [],
"source": [
"df <- data.frame(\n",
" \"Sample\" = c(\"S1\", \"S2\", \"S3\", \"S4\", \"S5\", \"S6\", \"S7\", \"S8\", \"S9\"),\n",
" \"Treatment\" = c(rep(\"Control\", 3), rep(\"Placebo\", 3), rep(\"Treatment\", 3))\n",
")\n",
"df$Treatment <- factor(df$Treatment)\n",
"df\n",
"dim(df)\n",
"str(df)"
]
},
{
"cell_type": "markdown",
"id": "05afa9ad-c868-4daf-8888-3955c13b840a",
"metadata": {},
"source": [
"We can extract a specific column from a data frame either by using the matrix notation we learned earlier or we can use the __<i>$</i>__ notation. In this example, we convert the Treatment column to a factor (since this will be a covariate in a regression model)."
]
},
{
"cell_type": "markdown",
"id": "ab93f3b2-2d39-4ff0-aa2b-133ec38e7eca",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "fe142a58-f2f5-410d-a092-917e35bc3541",
"metadata": {},
"source": [
"<p><span style=\"font-size: 30px\"><b>Quizzes</b></span> <span style=\"float : inline;\">(run the command below to display the quizzes)</span> </p>"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d99c9655-a112-4f4e-a83a-d3829ed51f4c",
"metadata": {},
"outputs": [],
"source": [
"IRdisplay::display_html('<iframe src=\"quizes/Chapter2_Quizes.html\" width=100% height=450></iframe>')"
]
},
{
"cell_type": "markdown",
"id": "54072f27-c886-40f9-a768-8b3d521828cf",
"metadata": {},
"source": [
"# Why is this important?"
]
},
{
"cell_type": "markdown",
"id": "0054ece4-00e1-4362-97ca-20aadaf7f602",
"metadata": {},
"source": [
"In addition to being able to organize and tidy our data using different data structures, many of the functions in R require specific data types. For example, matrix operations tend to work on matrices and vectors and you might get errors or strange results if you try those operations on data frames. When we begin discussing regression, it will be important to know what types of data structures we are working with. In proteomics analysis, for example, we will use an input matrix of proteomic signal intensities, a design matrix that defines our model, and an error vector. The output of our regression model is a vector of our coefficients which is itself a weighting vector that indicates which covariate has the greatest effect on our dependent variable. Understanding our these data structures interact is an important skill in bioinformatics."
]
},
{
"cell_type": "markdown",
"id": "d73e41c6",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3fb2e3bb-25c6-470f-974d-faa0d86b6277",
"metadata": {},
"outputs": [],
"source": [
"sessionInfo()"
]
},
{
"cell_type": "markdown",
"id": "42fe3bdc",
"metadata": {},
"source": [
"---"
]
},
{
"cell_type": "markdown",
"id": "4f824d57",
"metadata": {},
"source": [
"## Conclusion\n",
"\n",
"This submodule provided a foundational overview of R data structures, crucial for effective biomedical data analysis and biomarker discovery. We explored fundamental data types like numeric, integer, character, and logical, along with special values like NA, NaN, and Inf, emphasizing the importance of recognizing and handling them correctly. The core data structures—vectors, lists, matrices, arrays, and data frames—were explained in terms of their dimensionality and homogeneity. We learned how to create, manipulate, and access elements within these structures, highlighting the benefits of vectorization in R for efficient computations. Understanding these principles is essential for building experimental objects, performing regression analysis, and interpreting the results in the context of biomarker discovery, as demonstrated by the example of proteomics data analysis. Proper data structure management ensures compatibility with R functions and allows for a clear understanding of data organization and relationships, ultimately facilitating insightful data exploration and analysis in biomedical research."
]
},
{
"cell_type": "markdown",
"id": "b1870d8f",
"metadata": {},
"source": [
"## Clean up\n",
"\n",
"Remember to move to the next notebook or shut down your instance if you are finished."
]
}
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 5
}