.. _compare_with_r:
{{ header }}
Comparison with R / R libraries
*******************************
Since pandas aims to provide a lot of the data manipulation and analysis
functionality that people use `R `__ for, this page
was started to provide a more detailed look at the `R language
`__ and its many third
party libraries as they relate to pandas. In comparisons with R and CRAN
libraries, we care about the following things:
* **Functionality / flexibility**: what can/cannot be done with each tool
* **Performance**: how fast are operations. Hard numbers/benchmarks are
preferable
* **Ease-of-use**: Is one tool easier/harder to use (you may have to be
the judge of this, given side-by-side code comparisons)
This page is also here to offer a bit of a translation guide for users of these
R packages.
Quick reference
---------------
We'll start off with a quick reference guide pairing some common R
operations using `dplyr
`__ with
pandas equivalents.
Querying, filtering, sampling
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
=========================================== ===========================================
R pandas
=========================================== ===========================================
``dim(df)`` ``df.shape``
``head(df)`` ``df.head()``
``slice(df, 1:10)`` ``df.iloc[:9]``
``filter(df, col1 == 1, col2 == 1)`` ``df.query('col1 == 1 & col2 == 1')``
``df[df$col1 == 1 & df$col2 == 1,]`` ``df[(df.col1 == 1) & (df.col2 == 1)]``
``select(df, col1, col2)`` ``df[['col1', 'col2']]``
``select(df, col1:col3)`` ``df.loc[:, 'col1':'col3']``
``select(df, -(col1:col3))`` ``df.drop(cols_to_drop, axis=1)`` but see [#select_range]_
``distinct(select(df, col1))`` ``df[['col1']].drop_duplicates()``
``distinct(select(df, col1, col2))`` ``df[['col1', 'col2']].drop_duplicates()``
``sample_n(df, 10)`` ``df.sample(n=10)``
``sample_frac(df, 0.01)`` ``df.sample(frac=0.01)``
=========================================== ===========================================
.. [#select_range] R's shorthand for a subrange of columns
(``select(df, col1:col3)``) can be approached
cleanly in pandas, if you have the list of columns,
for example ``df[cols[1:3]]`` or
``df.drop(cols[1:3])``, but doing this by column
name is a bit messy.
Sorting
~~~~~~~
=========================================== ===========================================
R pandas
=========================================== ===========================================
``arrange(df, col1, col2)`` ``df.sort_values(['col1', 'col2'])``
``arrange(df, desc(col1))`` ``df.sort_values('col1', ascending=False)``
=========================================== ===========================================
Transforming
~~~~~~~~~~~~
=========================================== ===========================================
R pandas
=========================================== ===========================================
``select(df, col_one = col1)`` ``df.rename(columns={'col1': 'col_one'})['col_one']``
``rename(df, col_one = col1)`` ``df.rename(columns={'col1': 'col_one'})``
``mutate(df, c=a-b)`` ``df.assign(c=df['a']-df['b'])``
=========================================== ===========================================
Grouping and summarizing
~~~~~~~~~~~~~~~~~~~~~~~~
============================================== ===========================================
R pandas
============================================== ===========================================
``summary(df)`` ``df.describe()``
``gdf <- group_by(df, col1)`` ``gdf = df.groupby('col1')``
``summarise(gdf, avg=mean(col1, na.rm=TRUE))`` ``df.groupby('col1').agg({'col1': 'mean'})``
``summarise(gdf, total=sum(col1))`` ``df.groupby('col1').sum()``
============================================== ===========================================
Base R
------
Slicing with R's |c|_
~~~~~~~~~~~~~~~~~~~~~
R makes it easy to access ``data.frame`` columns by name
.. code-block:: r
df <- data.frame(a=rnorm(5), b=rnorm(5), c=rnorm(5), d=rnorm(5), e=rnorm(5))
df[, c("a", "c", "e")]
or by integer location
.. code-block:: r
df <- data.frame(matrix(rnorm(1000), ncol=100))
df[, c(1:10, 25:30, 40, 50:100)]
Selecting multiple columns by name in pandas is straightforward
.. ipython:: python
df = pd.DataFrame(np.random.randn(10, 3), columns=list("abc"))
df[["a", "c"]]
df.loc[:, ["a", "c"]]
Selecting multiple noncontiguous columns by integer location can be achieved
with a combination of the ``iloc`` indexer attribute and ``numpy.r_``.
.. ipython:: python
named = list("abcdefg")
n = 30
columns = named + np.arange(len(named), n).tolist()
df = pd.DataFrame(np.random.randn(n, n), columns=columns)
df.iloc[:, np.r_[:10, 24:30]]
|aggregate|_
~~~~~~~~~~~~
In R you may want to split data into subsets and compute the mean for each.
Using a data.frame called ``df`` and splitting it into groups ``by1`` and
``by2``:
.. code-block:: r
df <- data.frame(
v1 = c(1,3,5,7,8,3,5,NA,4,5,7,9),
v2 = c(11,33,55,77,88,33,55,NA,44,55,77,99),
by1 = c("red", "blue", 1, 2, NA, "big", 1, 2, "red", 1, NA, 12),
by2 = c("wet", "dry", 99, 95, NA, "damp", 95, 99, "red", 99, NA, NA))
aggregate(x=df[, c("v1", "v2")], by=list(mydf2$by1, mydf2$by2), FUN = mean)
The :meth:`~pandas.DataFrame.groupby` method is similar to base R ``aggregate``
function.
.. ipython:: python
df = pd.DataFrame(
{
"v1": [1, 3, 5, 7, 8, 3, 5, np.nan, 4, 5, 7, 9],
"v2": [11, 33, 55, 77, 88, 33, 55, np.nan, 44, 55, 77, 99],
"by1": ["red", "blue", 1, 2, np.nan, "big", 1, 2, "red", 1, np.nan, 12],
"by2": [
"wet",
"dry",
99,
95,
np.nan,
"damp",
95,
99,
"red",
99,
np.nan,
np.nan,
],
}
)
g = df.groupby(["by1", "by2"])
g[["v1", "v2"]].mean()
For more details and examples see :ref:`the groupby documentation
`.
|match|_
~~~~~~~~~~~~
A common way to select data in R is using ``%in%`` which is defined using the
function ``match``. The operator ``%in%`` is used to return a logical vector
indicating if there is a match or not:
.. code-block:: r
s <- 0:4
s %in% c(2,4)
The :meth:`~pandas.DataFrame.isin` method is similar to R ``%in%`` operator:
.. ipython:: python
s = pd.Series(np.arange(5), dtype=np.float32)
s.isin([2, 4])
The ``match`` function returns a vector of the positions of matches
of its first argument in its second:
.. code-block:: r
s <- 0:4
match(s, c(2,4))
For more details and examples see :ref:`the reshaping documentation
`.
|tapply|_
~~~~~~~~~
``tapply`` is similar to ``aggregate``, but data can be in a ragged array,
since the subclass sizes are possibly irregular. Using a data.frame called
``baseball``, and retrieving information based on the array ``team``:
.. code-block:: r
baseball <-
data.frame(team = gl(5, 5,
labels = paste("Team", LETTERS[1:5])),
player = sample(letters, 25),
batting.average = runif(25, .200, .400))
tapply(baseball$batting.average, baseball.example$team,
max)
In pandas we may use :meth:`~pandas.pivot_table` method to handle this:
.. ipython:: python
import random
import string
baseball = pd.DataFrame(
{
"team": ["team %d" % (x + 1) for x in range(5)] * 5,
"player": random.sample(list(string.ascii_lowercase), 25),
"batting avg": np.random.uniform(0.200, 0.400, 25),
}
)
baseball.pivot_table(values="batting avg", columns="team", aggfunc="max")
For more details and examples see :ref:`the reshaping documentation
`.
|subset|_
~~~~~~~~~~
The :meth:`~pandas.DataFrame.query` method is similar to the base R ``subset``
function. In R you might want to get the rows of a ``data.frame`` where one
column's values are less than another column's values:
.. code-block:: r
df <- data.frame(a=rnorm(10), b=rnorm(10))
subset(df, a <= b)
df[df$a <= df$b,] # note the comma
In pandas, there are a few ways to perform subsetting. You can use
:meth:`~pandas.DataFrame.query` or pass an expression as if it were an
index/slice as well as standard boolean indexing:
.. ipython:: python
df = pd.DataFrame({"a": np.random.randn(10), "b": np.random.randn(10)})
df.query("a <= b")
df[df["a"] <= df["b"]]
df.loc[df["a"] <= df["b"]]
For more details and examples see :ref:`the query documentation
`.
|with|_
~~~~~~~~
An expression using a data.frame called ``df`` in R with the columns ``a`` and
``b`` would be evaluated using ``with`` like so:
.. code-block:: r
df <- data.frame(a=rnorm(10), b=rnorm(10))
with(df, a + b)
df$a + df$b # same as the previous expression
In pandas the equivalent expression, using the
:meth:`~pandas.DataFrame.eval` method, would be:
.. ipython:: python
df = pd.DataFrame({"a": np.random.randn(10), "b": np.random.randn(10)})
df.eval("a + b")
df["a"] + df["b"] # same as the previous expression
In certain cases :meth:`~pandas.DataFrame.eval` will be much faster than
evaluation in pure Python. For more details and examples see :ref:`the eval
documentation `.
plyr
----
``plyr`` is an R library for the split-apply-combine strategy for data
analysis. The functions revolve around three data structures in R, ``a``
for ``arrays``, ``l`` for ``lists``, and ``d`` for ``data.frame``. The
table below shows how these data structures could be mapped in Python.
+------------+-------------------------------+
| R | Python |
+============+===============================+
| array | list |
+------------+-------------------------------+
| lists | dictionary or list of objects |
+------------+-------------------------------+
| data.frame | dataframe |
+------------+-------------------------------+
ddply
~~~~~
An expression using a data.frame called ``df`` in R where you want to
summarize ``x`` by ``month``:
.. code-block:: r
require(plyr)
df <- data.frame(
x = runif(120, 1, 168),
y = runif(120, 7, 334),
z = runif(120, 1.7, 20.7),
month = rep(c(5,6,7,8),30),
week = sample(1:4, 120, TRUE)
)
ddply(df, .(month, week), summarize,
mean = round(mean(x), 2),
sd = round(sd(x), 2))
In pandas the equivalent expression, using the
:meth:`~pandas.DataFrame.groupby` method, would be:
.. ipython:: python
df = pd.DataFrame(
{
"x": np.random.uniform(1.0, 168.0, 120),
"y": np.random.uniform(7.0, 334.0, 120),
"z": np.random.uniform(1.7, 20.7, 120),
"month": [5, 6, 7, 8] * 30,
"week": np.random.randint(1, 4, 120),
}
)
grouped = df.groupby(["month", "week"])
grouped["x"].agg(["mean", "std"])
For more details and examples see :ref:`the groupby documentation
`.
reshape / reshape2
------------------
meltarray
~~~~~~~~~
An expression using a 3 dimensional array called ``a`` in R where you want to
melt it into a data.frame:
.. code-block:: r
a <- array(c(1:23, NA), c(2,3,4))
data.frame(melt(a))
In Python, since ``a`` is a list, you can simply use list comprehension.
.. ipython:: python
a = np.array(list(range(1, 24)) + [np.NAN]).reshape(2, 3, 4)
pd.DataFrame([tuple(list(x) + [val]) for x, val in np.ndenumerate(a)])
meltlist
~~~~~~~~
An expression using a list called ``a`` in R where you want to melt it
into a data.frame:
.. code-block:: r
a <- as.list(c(1:4, NA))
data.frame(melt(a))
In Python, this list would be a list of tuples, so
:meth:`~pandas.DataFrame` method would convert it to a dataframe as required.
.. ipython:: python
a = list(enumerate(list(range(1, 5)) + [np.NAN]))
pd.DataFrame(a)
For more details and examples see :ref:`the Into to Data Structures
documentation `.
meltdf
~~~~~~
An expression using a data.frame called ``cheese`` in R where you want to
reshape the data.frame:
.. code-block:: r
cheese <- data.frame(
first = c('John', 'Mary'),
last = c('Doe', 'Bo'),
height = c(5.5, 6.0),
weight = c(130, 150)
)
melt(cheese, id=c("first", "last"))
In Python, the :meth:`~pandas.melt` method is the R equivalent:
.. ipython:: python
cheese = pd.DataFrame(
{
"first": ["John", "Mary"],
"last": ["Doe", "Bo"],
"height": [5.5, 6.0],
"weight": [130, 150],
}
)
pd.melt(cheese, id_vars=["first", "last"])
cheese.set_index(["first", "last"]).stack(future_stack=True) # alternative way
For more details and examples see :ref:`the reshaping documentation
`.
cast
~~~~
In R ``acast`` is an expression using a data.frame called ``df`` in R to cast
into a higher dimensional array:
.. code-block:: r
df <- data.frame(
x = runif(12, 1, 168),
y = runif(12, 7, 334),
z = runif(12, 1.7, 20.7),
month = rep(c(5,6,7),4),
week = rep(c(1,2), 6)
)
mdf <- melt(df, id=c("month", "week"))
acast(mdf, week ~ month ~ variable, mean)
In Python the best way is to make use of :meth:`~pandas.pivot_table`:
.. ipython:: python
df = pd.DataFrame(
{
"x": np.random.uniform(1.0, 168.0, 12),
"y": np.random.uniform(7.0, 334.0, 12),
"z": np.random.uniform(1.7, 20.7, 12),
"month": [5, 6, 7] * 4,
"week": [1, 2] * 6,
}
)
mdf = pd.melt(df, id_vars=["month", "week"])
pd.pivot_table(
mdf,
values="value",
index=["variable", "week"],
columns=["month"],
aggfunc="mean",
)
Similarly for ``dcast`` which uses a data.frame called ``df`` in R to
aggregate information based on ``Animal`` and ``FeedType``:
.. code-block:: r
df <- data.frame(
Animal = c('Animal1', 'Animal2', 'Animal3', 'Animal2', 'Animal1',
'Animal2', 'Animal3'),
FeedType = c('A', 'B', 'A', 'A', 'B', 'B', 'A'),
Amount = c(10, 7, 4, 2, 5, 6, 2)
)
dcast(df, Animal ~ FeedType, sum, fill=NaN)
# Alternative method using base R
with(df, tapply(Amount, list(Animal, FeedType), sum))
Python can approach this in two different ways. Firstly, similar to above
using :meth:`~pandas.pivot_table`:
.. ipython:: python
df = pd.DataFrame(
{
"Animal": [
"Animal1",
"Animal2",
"Animal3",
"Animal2",
"Animal1",
"Animal2",
"Animal3",
],
"FeedType": ["A", "B", "A", "A", "B", "B", "A"],
"Amount": [10, 7, 4, 2, 5, 6, 2],
}
)
df.pivot_table(values="Amount", index="Animal", columns="FeedType", aggfunc="sum")
The second approach is to use the :meth:`~pandas.DataFrame.groupby` method:
.. ipython:: python
df.groupby(["Animal", "FeedType"])["Amount"].sum()
For more details and examples see :ref:`the reshaping documentation
` or :ref:`the groupby documentation`.
|factor|_
~~~~~~~~~
pandas has a data type for categorical data.
.. code-block:: r
cut(c(1,2,3,4,5,6), 3)
factor(c(1,2,3,2,2,3))
In pandas this is accomplished with ``pd.cut`` and ``astype("category")``:
.. ipython:: python
pd.cut(pd.Series([1, 2, 3, 4, 5, 6]), 3)
pd.Series([1, 2, 3, 2, 2, 3]).astype("category")
For more details and examples see :ref:`categorical introduction ` and the
:ref:`API documentation `. There is also a documentation regarding the
:ref:`differences to R's factor `.
.. |c| replace:: ``c``
.. _c: https://stat.ethz.ch/R-manual/R-patched/library/base/html/c.html
.. |aggregate| replace:: ``aggregate``
.. _aggregate: https://stat.ethz.ch/R-manual/R-patched/library/stats/html/aggregate.html
.. |match| replace:: ``match`` / ``%in%``
.. _match: https://stat.ethz.ch/R-manual/R-patched/library/base/html/match.html
.. |tapply| replace:: ``tapply``
.. _tapply: https://stat.ethz.ch/R-manual/R-patched/library/base/html/tapply.html
.. |with| replace:: ``with``
.. _with: https://stat.ethz.ch/R-manual/R-patched/library/base/html/with.html
.. |subset| replace:: ``subset``
.. _subset: https://stat.ethz.ch/R-manual/R-patched/library/base/html/subset.html
.. |factor| replace:: ``factor``
.. _factor: https://stat.ethz.ch/R-manual/R-devel/library/base/html/factor.html