{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "54c1e09c",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:22.844537Z",
     "iopub.status.busy": "2022-07-31T22:12:22.843906Z",
     "iopub.status.idle": "2022-07-31T22:12:23.501852Z",
     "shell.execute_reply": "2022-07-31T22:12:23.502129Z"
    }
   },
   "outputs": [],
   "source": [
    "from functools import partial\n",
    "from rpy2.ipython import html\n",
    "html.html_rdataframe=partial(html.html_rdataframe, table_class=\"docutils\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b5dbb6c7",
   "metadata": {},
   "source": [
    "# Basic handling\n",
    "\n",
    "The S4 system is one the OOP systems in R.\n",
    "Its largest use might be in the Bioconductor collection of packages\n",
    "for bioinformatics and computational biology.\n",
    "\n",
    "We use the bioconductor `Biobase`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "19c9d50e",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:23.504824Z",
     "iopub.status.busy": "2022-07-31T22:12:23.504466Z",
     "iopub.status.idle": "2022-07-31T22:12:23.931619Z",
     "shell.execute_reply": "2022-07-31T22:12:23.931880Z"
    }
   },
   "outputs": [],
   "source": [
    "from rpy2.robjects.packages import importr\n",
    "biobase = importr('Biobase')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f515a9b1",
   "metadata": {},
   "source": [
    "The R package contains constructors for the S4 classes defined. They\n",
    "are simply functions, and can be used as such through `rpy2`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "cc1694c9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:23.934267Z",
     "iopub.status.busy": "2022-07-31T22:12:23.933949Z",
     "iopub.status.idle": "2022-07-31T22:12:23.989943Z",
     "shell.execute_reply": "2022-07-31T22:12:23.989489Z"
    }
   },
   "outputs": [],
   "source": [
    "eset = biobase.ExpressionSet() "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9eee0d37",
   "metadata": {},
   "source": [
    "The object `eset` is an R object of type `S4`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "65feebf5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:23.997312Z",
     "iopub.status.busy": "2022-07-31T22:12:23.996855Z",
     "iopub.status.idle": "2022-07-31T22:12:23.999542Z",
     "shell.execute_reply": "2022-07-31T22:12:23.999146Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "rpy2.robjects.methods.RS4"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "type(eset)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eef74d64",
   "metadata": {},
   "source": [
    "It has a class as well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "ca64d156",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.002551Z",
     "iopub.status.busy": "2022-07-31T22:12:24.002077Z",
     "iopub.status.idle": "2022-07-31T22:12:24.004803Z",
     "shell.execute_reply": "2022-07-31T22:12:24.004422Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('ExpressionSet',)"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tuple(eset.rclass)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8e17bf80",
   "metadata": {},
   "source": [
    "In R, objects attributes are also known as slots. The attribute names\n",
    "can be listed with:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "5ad645af",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.007427Z",
     "iopub.status.busy": "2022-07-31T22:12:24.006979Z",
     "iopub.status.idle": "2022-07-31T22:12:24.010172Z",
     "shell.execute_reply": "2022-07-31T22:12:24.009808Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "('experimentData',\n",
       " 'assayData',\n",
       " 'phenoData',\n",
       " 'featureData',\n",
       " 'annotation',\n",
       " 'protocolData',\n",
       " '.__classVersion__')"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "tuple(eset.slotnames())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "77c44102",
   "metadata": {},
   "source": [
    "The attributes can also be accessed through the `rpy2` property `slots`.\n",
    "`slots` is a mapping between attributes names (keys) and their associated\n",
    "R object (values). It can be used as Python `dict`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "b132b760",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.013036Z",
     "iopub.status.busy": "2022-07-31T22:12:24.012499Z",
     "iopub.status.idle": "2022-07-31T22:12:24.015444Z",
     "shell.execute_reply": "2022-07-31T22:12:24.015711Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('.__classVersion__', 'experimentData', 'assayData', 'phenoData', 'featureData', 'annotation', 'protocolData', 'class')\n"
     ]
    }
   ],
   "source": [
    "# print keys\n",
    "print(tuple(eset.slots.keys()))\n",
    "\n",
    "# fetch `phenoData`\n",
    "phdat = eset.slots['phenoData']\n",
    "\n",
    "# phdat is an S4 object itself\n",
    "pheno_dataf = phdat.slots['data']"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "75ab9d5c",
   "metadata": {},
   "source": [
    "# Mapping S4 classes to Python classes\n",
    "\n",
    "Writing one's own Python class extending rpy2's `RS4` is straightforward.\n",
    "That class can be used wrap our `eset` object"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "32b49a77",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.018076Z",
     "iopub.status.busy": "2022-07-31T22:12:24.017752Z",
     "iopub.status.idle": "2022-07-31T22:12:24.019429Z",
     "shell.execute_reply": "2022-07-31T22:12:24.019137Z"
    }
   },
   "outputs": [],
   "source": [
    "\n",
    "from rpy2.robjects.methods import RS4   \n",
    "class ExpressionSet(RS4):\n",
    "    pass\n",
    "\n",
    "eset_myclass = ExpressionSet(eset)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "07d5988a",
   "metadata": {},
   "source": [
    "## Custom conversion\n",
    "\n",
    "The conversion system can also be made aware our new class by customizing\n",
    "the handling of S4 objects.\n",
    "\n",
    "A simple implementation is a factory function that will conditionally wrap\n",
    "the object in our Python class `ExpressionSet`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "777f9876",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.022461Z",
     "iopub.status.busy": "2022-07-31T22:12:24.021999Z",
     "iopub.status.idle": "2022-07-31T22:12:24.024230Z",
     "shell.execute_reply": "2022-07-31T22:12:24.024500Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.ExpressionSet object at 0x7fc4a9fc4340> [RTYPES.S4SXP]\n",
       "R classes: ('ExpressionSet',)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "def rpy2py_s4(obj):\n",
    "    if 'ExpressionSet' in obj.rclass:\n",
    "        res = ExpressionSet(obj)\n",
    "    else:\n",
    "        res = robj\n",
    "    return res\n",
    "\n",
    "# try it\n",
    "rpy2py_s4(eset)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9bf94f60",
   "metadata": {},
   "source": [
    "That function can be be register to a `Converter`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "f6c3a4a5",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.027525Z",
     "iopub.status.busy": "2022-07-31T22:12:24.027151Z",
     "iopub.status.idle": "2022-07-31T22:12:24.029014Z",
     "shell.execute_reply": "2022-07-31T22:12:24.029262Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<function __main__.rpy2py_s4(obj)>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from rpy2.robjects import default_converter\n",
    "from rpy2.robjects.conversion import Converter, localconverter\n",
    "\n",
    "my_converter = Converter('ExpressionSet-aware converter',\n",
    "                         template=default_converter)\n",
    "\n",
    "from rpy2.rinterface import SexpS4\n",
    "my_converter.rpy2py.register(SexpS4, rpy2py_s4)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a05661e1",
   "metadata": {},
   "source": [
    "When using that converter, the matching R objects are returned as\n",
    "instances of our Python class `ExpressionSet`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "7a5753f8",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.031899Z",
     "iopub.status.busy": "2022-07-31T22:12:24.031394Z",
     "iopub.status.idle": "2022-07-31T22:12:24.065965Z",
     "shell.execute_reply": "2022-07-31T22:12:24.066255Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "<class '__main__.ExpressionSet'>\n"
     ]
    }
   ],
   "source": [
    "\n",
    "with localconverter(my_converter) as cv:\n",
    "    eset = biobase.ExpressionSet()\n",
    "    print(type(eset))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5279703",
   "metadata": {},
   "source": [
    "## Class attributes\n",
    "\n",
    "The R attribute `assayData` can be accessed\n",
    "through the accessor method `exprs()` in R.\n",
    "We can make it a property in our Python class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "ab4669de",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.070354Z",
     "iopub.status.busy": "2022-07-31T22:12:24.069954Z",
     "iopub.status.idle": "2022-07-31T22:12:24.072476Z",
     "shell.execute_reply": "2022-07-31T22:12:24.072120Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<rpy2.robjects.environments.Environment object at 0x7fc4a9fd5080> [RTYPES.ENVSXP]\n",
       "R classes: ('environment',)\n",
       "n items: 1"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class ExpressionSet(RS4):\n",
    "    def _exprs_get(self):\n",
    "        return self.slots['assayData']\n",
    "    def _exprs_set(self, value):\n",
    "        self.slots['assayData'] = value\n",
    "    exprs = property(_exprs_get,\n",
    "                     _exprs_set,\n",
    "                     None,\n",
    "                     \"R attribute `exprs`\")\n",
    "eset_myclass = ExpressionSet(eset)\n",
    "\n",
    "eset_myclass.exprs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbde7dad",
   "metadata": {},
   "source": [
    "## Methods\n",
    "\n",
    "In R's S4 methods are generic functions served by a multiple dispatch system.\n",
    "\n",
    "A natural way to expose the S4 method to Python is to use the\n",
    "`multipledispatch` package:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "61ef6c7a",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.075929Z",
     "iopub.status.busy": "2022-07-31T22:12:24.075391Z",
     "iopub.status.idle": "2022-07-31T22:12:24.078925Z",
     "shell.execute_reply": "2022-07-31T22:12:24.079228Z"
    }
   },
   "outputs": [],
   "source": [
    "from multipledispatch import dispatch\n",
    "from functools import partial\n",
    "\n",
    "my_namespace = dict()\n",
    "dispatch = partial(dispatch, namespace=my_namespace)\n",
    "\n",
    "@dispatch(ExpressionSet)\n",
    "def rowmedians(eset,\n",
    "               na_rm=False):\n",
    "    res = biobase.rowMedians(eset,\n",
    "                             na_rm=na_rm)\n",
    "    return res\n",
    "\n",
    "res = rowmedians(eset_myclass)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ac6b3e4a",
   "metadata": {},
   "source": [
    "The R method `rowMedians` is also defined for matrices, which we can expose\n",
    "on the Python end as well:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "2b2b7d19",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.082342Z",
     "iopub.status.busy": "2022-07-31T22:12:24.081956Z",
     "iopub.status.idle": "2022-07-31T22:12:24.083579Z",
     "shell.execute_reply": "2022-07-31T22:12:24.083857Z"
    }
   },
   "outputs": [],
   "source": [
    "from rpy2.robjects.vectors import Matrix\n",
    "@dispatch(Matrix)\n",
    "def rowmedians(m,\n",
    "               na_rm=False):\n",
    "    res = biobase.rowMedians(m,\n",
    "                             na_rm=na_rm)\n",
    "    return res"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c6b43de7",
   "metadata": {},
   "source": [
    "While this is working, one can note that we call the same R function\n",
    "`rowMedians()` in the package `Biobase` in both Python decorated\n",
    "functions. What is happening is that the dispatch is performed by R.\n",
    "\n",
    "If this is ever becoming a performance issue, the specific R function\n",
    "dispatched can be prefetched and explicitly called in the Python\n",
    "function. For example:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "5fcc2de1",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2022-07-31T22:12:24.087284Z",
     "iopub.status.busy": "2022-07-31T22:12:24.086814Z",
     "iopub.status.idle": "2022-07-31T22:12:24.088931Z",
     "shell.execute_reply": "2022-07-31T22:12:24.089232Z"
    }
   },
   "outputs": [],
   "source": [
    "from rpy2.robjects.methods import getmethod\n",
    "from rpy2.robjects.vectors import StrVector\n",
    "_rowmedians_matrix = getmethod(StrVector([\"rowMedians\"]),\n",
    "                               signature=StrVector([\"matrix\"]))\n",
    "@dispatch(Matrix)\n",
    "def rowmedians(m,\n",
    "               na_rm=False):\n",
    "    res = _rowmedians_matrix(m,\n",
    "                             na_rm=na_rm)\n",
    "    return res"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
