diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56e7c57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.local/ +.ipynb_checkpoints/ +.cache/ diff --git a/Dockerfile b/Dockerfile index 7507012..9d2a939 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,15 @@ -ARG BASE_CONTAINER=jupyter/datascience-notebook:hub-1.1.0 -ARG DATAHUB_CONTAINER=ucsdets/datahub-base-notebook:2020.2-stable +# ARG BASE_CONTAINER=jupyter/datascience-notebook:hub-1.1.0 +ARG DATAHUB_CONTAINER=ucsdets/datahub-base-notebook-test:2020.2-stable # # force rebuild FROM $DATAHUB_CONTAINER as datahub -FROM $BASE_CONTAINER +# FROM $BASE_CONTAINER MAINTAINER UC San Diego ITS/ETS-EdTech-Ecosystems -COPY --from=datahub /usr/share/datahub/scripts/* /usr/share/datahub/scripts/ -COPY --from=datahub /usr/share/datahub/tests /usr/share/datahub/tests -RUN /usr/share/datahub/scripts/install-all.sh - # Install OKpy for DSC courses # downgrade pip temporarily and upgrade to fix issue with okpy install +USER root RUN pip install --upgrade --force-reinstall pip==9.0.3 RUN pip install okpy --disable-pip-version-check RUN pip install --upgrade pip @@ -26,18 +23,15 @@ RUN python -c 'import matplotlib.pyplot' RUN conda clean -tipsy -# Run container integration tests -USER root +# import integration tests ENV TESTDIR=/usr/share/datahub/tests ARG DATASCIENCE_TESTDIR=${TESTDIR}/datascience-notebook COPY tests ${DATASCIENCE_TESTDIR} -RUN chmod -R +x ${TESTDIR} -RUN for f in ${TESTDIR}/**/*.sh; do bash $f; done +RUN chmod -R +rwx ${DATASCIENCE_TESTDIR} +RUN chown 1000:1000 ${DATASCIENCE_TESTDIR} # change the owner back RUN chown -R 1000:1000 /home/jovyan USER $NB_UID RUN bash -c 'find /opt/julia -type f -a -name "*.ji" -a \! -perm /005 | xargs chmod og+rX' ENV SHELL=/bin/bash - -COPY --from=datahub /run_jupyter.sh / diff --git a/README.md b/README.md index 5ec55ae..0dc540b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ # Changelog - 2019.4.4 2019.4.3 diff --git a/bin/test b/bin/test new file mode 100755 index 0000000..f6d1605 --- /dev/null +++ b/bin/test @@ -0,0 +1,6 @@ +#!/bin/bash + +# Used for development purposes + +docker-compose -f docker-compose.test.yml build +docker-compose -f docker-compose.test.yml up --remove-orphans \ No newline at end of file diff --git a/docker-compose.test.yml b/docker-compose.test.yml new file mode 100644 index 0000000..2bc2abb --- /dev/null +++ b/docker-compose.test.yml @@ -0,0 +1,4 @@ +sut: + build: . + command: /usr/share/datahub/tests/datascience-notebook/test.sh + user: root \ No newline at end of file diff --git a/tests/.bash_history b/tests/.bash_history new file mode 100644 index 0000000..5c78752 --- /dev/null +++ b/tests/.bash_history @@ -0,0 +1,7 @@ +ls +jupyter nbconvert --to r datascience_notebook.ipynb +jupyter nbconvert --to +jupyter nbconvert --to -h +jupyter nbconvert --execute datascience_notebook.ipynb +exit +exit diff --git a/tests/addressbook.proto b/tests/addressbook.proto deleted file mode 100644 index 77883c1..0000000 --- a/tests/addressbook.proto +++ /dev/null @@ -1,26 +0,0 @@ -syntax = "proto2"; - -package tutorial; - -message Person { - required string name = 1; - required int32 id = 2; - optional string email = 3; - - enum PhoneType { - MOBILE = 0; - HOME = 1; - WORK = 2; - } - - message PhoneNumber { - required string number = 1; - optional PhoneType type = 2 [default = HOME]; - } - - repeated PhoneNumber phones = 4; -} - -message AddressBook { - repeated Person people = 1; -} \ No newline at end of file diff --git a/tests/datascience_notebook.html b/tests/datascience_notebook.html new file mode 100644 index 0000000..f0673fa --- /dev/null +++ b/tests/datascience_notebook.html @@ -0,0 +1,13440 @@ + + + + +datascience_notebook + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+
+
In [1]:
+
+
+
myString <- "Hello, World!"
+
+
+par(mfrow=c(1, 2))  # divide graph area in 2 columns
+boxplot(cars$speed, main="Speed", sub=paste("Outlier rows: ", boxplot.stats(cars$speed)$out))  # box plot for 'speed'
+boxplot(cars$dist, main="Distance", sub=paste("Outlier rows: ", boxplot.stats(cars$dist)$out))  # box plot for 'distance'
+
+ +
+
+
+ +
+
+ + +
+ +
+ + + + +
+ +
+ +
+ +
+
+ +
+
+
+
In [2]:
+
+
+
cor(cars$speed, cars$dist)  # calculate correlation between speed and distance 
+linearMod <- lm(dist ~ speed, data=cars)  # build linear regression model on full data
+print(linearMod)
+summary(linearMod)  # model summary
+
+ +
+
+
+ +
+
+ + +
+ +
+ + + +
+0.80689490068921 +
+ +
+ +
+ +
+ + +
+
+Call:
+lm(formula = dist ~ speed, data = cars)
+
+Coefficients:
+(Intercept)        speed  
+    -17.579        3.932  
+
+
+
+
+ +
+ +
+ + + + +
+
+Call:
+lm(formula = dist ~ speed, data = cars)
+
+Residuals:
+    Min      1Q  Median      3Q     Max 
+-29.069  -9.525  -2.272   9.215  43.201 
+
+Coefficients:
+            Estimate Std. Error t value Pr(>|t|)    
+(Intercept) -17.5791     6.7584  -2.601   0.0123 *  
+speed         3.9324     0.4155   9.464 1.49e-12 ***
+---
+Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
+
+Residual standard error: 15.38 on 48 degrees of freedom
+Multiple R-squared:  0.6511,	Adjusted R-squared:  0.6438 
+F-statistic: 89.57 on 1 and 48 DF,  p-value: 1.49e-12
+
+
+ +
+ +
+
+ +
+
+
+ + + + + + diff --git a/tests/datascience_notebook.ipynb b/tests/datascience_notebook.ipynb index 98982f0..1c9f46e 100644 --- a/tests/datascience_notebook.ipynb +++ b/tests/datascience_notebook.ipynb @@ -1,410 +1,135 @@ { - "nbformat": 4, - "nbformat_minor": 2, - "metadata": { - "language_info": { - "name": "python", - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "version": "3.6.8-final" - }, - "orig_nbformat": 2, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "npconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": 3, - "kernelspec": { - "name": "python36864bitaed85d4181ba4aae8deef210d1b174de", - "display_name": "Python 3.6.8 64-bit" - } - }, "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Inherited packages\n", - " 'beautifulsoup4=4.8.*' \\\n", - " 'conda-forge::blas=*=openblas' \\\n", - " 'bokeh=1.4.*' \\\n", - " 'cloudpickle=1.3.*' \\\n", - " 'cython=0.29.*' \\\n", - " 'dask=2.11.*' \\\n", - " 'dill=0.3.*' \\\n", - " 'h5py=2.10.*' \\\n", - " 'hdf5=1.10.*' \\\n", - " 'ipywidgets=7.5.*' \\\n", - " 'ipympl=0.5.*'\\\n", - " 'matplotlib-base=3.1.*' \\\n", - " 'numba=0.48.*' \\\n", - " 'numexpr=2.7.*' \\\n", - " 'pandas=1.0.*' \\\n", - " 'patsy=0.5.*' \\\n", - " 'protobuf=3.11.*' \\\n", - " 'scikit-image=0.16.*' \\\n", - " 'scikit-learn=0.22.*' \\\n", - " 'scipy=1.4.*' \\\n", - " 'seaborn=0.10.*' \\\n", - " 'sqlalchemy=1.3.*' \\\n", - " 'statsmodels=0.11.*' \\\n", - " 'sympy=1.5.*' \\\n", - " 'vincent=0.4.*' \\\n", - " 'widgetsnbextension=3.5.*'\\\n", - " 'xlrd' \\" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import ipywidgets # not testable without ui\n", - "import ipympl # not testable unless UI\n", - "import widgetsnbextension # not testable unless UI\n", - "import os\n", - "\n", - "TESTDIR = '/usr/share/datahub/tests/datascience-notebook'" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# beautifulsoup4, adapted from https://www.crummy.com/software/BeautifulSoup/bs4/doc/\n", - "from bs4 import BeautifulSoup\n", - "\n", - "html_doc = \"\"\"\n", - "The Dormouse's story\n", - "\n", - "

The Dormouse's story

\n", - "\n", - "

Once upon a time there were three little sisters; and their names were\n", - "Elsie,\n", - "Lacie and\n", - "Tillie;\n", - "and they lived at the bottom of a well.

\n", - "\n", - "

...

\n", - "\"\"\"\n", - "soup = BeautifulSoup(html_doc, 'html.parser')\n", - "\n", - "soup.title" - ] - }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from bokeh.plotting import figure, output_file, show\n", - "\n", - "# prepare some data\n", - "x = [1, 2, 3, 4, 5]\n", - "y = [6, 7, 2, 4, 5]\n", - "\n", - "# output to static HTML file\n", - "output_file(\"lines.html\")\n", - "\n", - "# create a new plot with a title and axis labels\n", - "p = figure(title=\"simple line example\", x_axis_label='x', y_axis_label='y')\n", - "\n", - "# add a line renderer with legend and line thickness\n", - "p.line(x, y, legend_label=\"Temp.\", line_width=2)\n", - "\n", - "# show the results\n", - "show(p)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# cloudpickle, referenced from https://github.com/cloudpipe/cloudpickle\n", - "import cloudpickle\n", - "squared = lambda x: x ** 2\n", - "pickled_lambda = cloudpickle.dumps(squared)\n", - "\n", - "import pickle\n", - "new_squared = pickle.loads(pickled_lambda)\n", - "assert new_squared(2) == 4" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# dask, referenced from https://examples.dask.org/array.html\n", - "from dask.distributed import Client, progress\n", - "client = Client(processes=False, threads_per_worker=4,\n", - " n_workers=1, memory_limit='2GB')\n", - "\n", - "import dask.array as da\n", - "x = da.random.random((10000, 10000), chunks=(1000, 1000))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import dill\n", - "import pandas as pd\n", - "\n", - "names = [\"John\", \"Mary\", \"Mary\", \"Suzanne\", \"John\", \"Suzanne\"]\n", - "scores = [80, 90, 90, 92, 95, 100]\n", - "\n", - "records = pd.DataFrame({\"name\": names, \"score\": scores})\n", - "means = records.groupby('name').mean()\n", - "\n", - "import dill as pickle\n", - "with open('name_model.pkl', 'wb') as file:\n", - " pickle.dump(means, file)\n", - "\n", - "with open('name_model.pkl', 'rb') as file:\n", - " B = pickle.load(file)\n", - "\n", - "def name_score_function(record):\n", - " if record in names:\n", - " return(means.loc[record, 'score'])\n", - "\n", - "assert name_score_function(\"John\") == 87.5" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# matplotlib, referenced from https://matplotlib.org/tutorials/introductory/pyplot.html\n", - "import matplotlib\n", - "import matplotlib.pyplot as plt\n", - "plt.plot([1, 2, 3, 4])\n", - "plt.ylabel('some numbers')\n", - "plt.show()\n", - "\n", - "savepath = os.path.join(TESTDIR, 'testplot.png')\n", - "plt.savefig(savepath)\n", - "assert os.path.isfile(savepath)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# skimage\n", - "from skimage import data\n", - "import os\n", - "\n", - "\n", - "IMG = os.path.join(TESTDIR, 'testfig.png')\n", - "\n", - "matplotlib.rcParams['font.size'] = 18\n", - "fig, axes = plt.subplots(1, 2, figsize=(8, 4))\n", - "ax = axes.ravel()\n", - "\n", - "images = data.stereo_motorcycle()\n", - "ax[0].imshow(images[0])\n", - "ax[1].imshow(images[1])\n", - "\n", - "fig.tight_layout()\n", - "plt.show()\n", - "plt.savefig(IMG)\n", - "\n", - "assert os.path.isfile(IMG)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from sympy import symbols\n", - "x, y = symbols('x y')\n", - "expr = x + 2*y" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# h5py, referenced from https://support.hdfgroup.org/ftp/HDF5/examples/Py/h5_crtdat.py\n", - "import h5py\n", - "filename = 'dset.h5'\n", - "file = h5py.File(os.path.join(TESTDIR, filename),'w')\n", - "dataset = file.create_dataset(\"dset\",(4, 6), h5py.h5t.STD_I32BE)\n", - "file.close()\n", - "\n", - "assert os.path.isfile(os.path.join(TESTDIR, filename))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# pandas, referenced from https://pandas.pydata.org/pandas-docs/stable/getting_started/10min.html\n", - "import pandas as pd\n", - "\n", - "dates = pd.date_range('20130101', periods=6)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from patsy import dmatrices, dmatrix, demo_data\n", - "\n", - "data = demo_data(\"a\", \"b\", \"x1\", \"x2\", \"y\", \"z column\")\n", - "dmatrices(\"y ~ x1 + x2\", data)\n", - "outcome, predictors = dmatrices(\"y ~ x1 + x2\", data)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# sklearn, referenced from https://scikit-learn.org/stable/tutorial/basic/tutorial.html\n", - "from sklearn import datasets, svm\n", - "iris = datasets.load_iris()\n", - "digits = datasets.load_digits()\n", - "clf = svm.SVC(gamma=0.001, C=100.)\n", - "clf.fit(digits.data[:-1], digits.target[:-1])\n", - "clf.predict(digits.data[-1:])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# scipy, adapted from https://docs.scipy.org/doc/scipy/reference/tutorial/optimize.html\n", - "import numpy as np\n", - "from scipy.optimize import minimize\n", - "\n", - "def rosen(x):\n", - " return sum(100.0*(x[1:]-x[:-1]**2.0)**2.0 + (1-x[:-1])**2.0)\n", - "\n", - "x0 = np.array([1.3, 0.7, 0.8, 1.9, 1.2])\n", - "res = minimize(rosen, x0, method='nelder-mead', options={'xatol': 1e-8, 'disp': True})" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# seaborn, https://seaborn.pydata.org/introduction.html and https://stackoverflow.com/questions/32244753/how-to-save-a-seaborn-plot-into-a-file\n", - "import seaborn as sns\n", - "sns.set()\n", - "tips = sns.load_dataset(\"tips\")\n", - "sns.relplot(x=\"total_bill\", y=\"tip\", col=\"time\",\n", - " hue=\"smoker\", style=\"smoker\", size=\"size\",\n", - " data=tips)\n", - "\n", - "savefile = os.path.join(TESTDIR, 'sns.png')\n", - "df = sns.load_dataset('iris')\n", - "sns_plot = sns.pairplot(df, hue='species', size=2.5)\n", - "sns_plot.savefig(savefile)\n", - "\n", - "assert os.path.isfile(savefile)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# sqlalchemy, adapted from https://leportella.com/english/2019/01/10/sqlalchemy-basics-tutorial.html\n", - "from sqlalchemy import create_engine\n", - "\n", - "engine = create_engine('sqlite:///:memory:', echo=True)\n", - "conn = engine.connect()\n", - "trans = conn.begin()\n", - "conn.execute('CREATE TABLE EX1 (name)')\n", - "conn.execute('INSERT INTO \"EX1\" (name) '\n", - " 'VALUES (\"Hello\")')\n", - "trans.commit()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# statsmodels, referenced from https://www.statsmodels.org/stable/examples/notebooks/generated/ols.html\n", - "import statsmodels.api as sm\n", - "import matplotlib.pyplot as plt\n", - "from statsmodels.sandbox.regression.predstd import wls_prediction_std\n", - "\n", - "np.random.seed(9876789)\n", - "nsample = 100\n", - "x = np.linspace(0, 10, 100)\n", - "X = np.column_stack((x, x**2))\n", - "beta = np.array([1, 0.1, 10])\n", - "e = np.random.normal(size=nsample)\n", - "X = sm.add_constant(X)\n", - "y = np.dot(X, beta) + e\n", - "\n", - "model = sm.OLS(y, X)\n", - "results = model.fit()\n", - "print(results.summary())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# vincent, https://vincent.readthedocs.io/en/latest/quickstart.html\n", - "import vincent\n", - "\n", - "list_data = [10, 20, 30, 20, 15, 30, 45]\n", - "bar = vincent.Bar(list_data)" + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0gAAANICAMAAADKOT/pAAAC/VBMVEUAAAABAQECAgIDAwME\nBAQFBQUGBgYHBwcICAgJCQkKCgoLCwsMDAwNDQ0ODg4PDw8QEBARERESEhITExMUFBQVFRUW\nFhYXFxcYGBgZGRkaGhobGxscHBwdHR0eHh4fHx8gICAhISEiIiIjIyMkJCQlJSUmJiYnJyco\nKCgpKSkqKiorKyssLCwtLS0uLi4vLy8wMDAxMTEyMjIzMzM0NDQ1NTU2NjY3Nzc4ODg5OTk6\nOjo7Ozs8PDw9PT0+Pj4/Pz9AQEBBQUFCQkJDQ0NERERFRUVGRkZHR0dISEhJSUlKSkpLS0tM\nTExNTU1OTk5PT09QUFBRUVFSUlJTU1NUVFRVVVVWVlZXV1dYWFhZWVlaWlpbW1tcXFxdXV1e\nXl5fX19gYGBhYWFiYmJjY2NkZGRlZWVmZmZnZ2dpaWlqampra2tsbGxtbW1ubm5vb29wcHBx\ncXFycnJzc3N0dHR1dXV2dnZ3d3d4eHh5eXl6enp7e3t8fHx9fX1+fn5/f3+AgICBgYGCgoKD\ng4OEhISFhYWGhoaHh4eIiIiJiYmKioqLi4uMjIyNjY2Ojo6Pj4+QkJCRkZGSkpKTk5OUlJSV\nlZWWlpaXl5eYmJiZmZmampqbm5ucnJydnZ2enp6fn5+goKChoaGioqKjo6OkpKSlpaWmpqan\np6eoqKipqamqqqqrq6usrKytra2urq6vr6+wsLCxsbGysrKzs7O0tLS1tbW2tra3t7e4uLi5\nubm6urq7u7u8vLy9vb2+vr6/v7/AwMDBwcHCwsLDw8PExMTFxcXGxsbHx8fIyMjJycnKysrL\ny8vMzMzNzc3Ozs7Pz8/Q0NDR0dHS0tLT09PU1NTV1dXW1tbX19fY2NjZ2dna2trb29vc3Nzd\n3d3e3t7f39/g4ODh4eHi4uLj4+Pk5OTl5eXm5ubn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v\n7+/w8PDx8fHy8vLz8/P09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz9/f3+/v7///+AusGv\nAAAACXBIWXMAABJ0AAASdAHeZh94AAAgAElEQVR4nO3dDZxVVb3w8TUMh4GBEUFFBBlA5fqS\nBYKVpRg3vJoYonnT0AqETAXFHvROeLngS4gXk655MbVbmmYlClZW6kOYZmqJPGYmpiPaRcP3\nIZXXcZj9efY5Z94WsP/MTGuv81+zf9/P587sM2cxnuF/f8w5e69mTATgH2ZK/QCAroCQAAcI\nCXCAkAAHCAlwgJAABwgJcICQAAcICXCAkAAHCAlwgJAABwgJcICQAAcICXCAkAAHCAlwgJAA\nBwgJcICQAAcICXCAkAAHCAlwgJAABwgJcICQAAcICXCAkAAHCAlwgJAABwgJcICQAAcICXCA\nkAAHCAlwgJAABwgJcICQAAcICXCAkJrcY0x5qR9DBtxlTEWpH0Mquk5IdYvG79cj1/+jl/y1\nU3+ckNLwXZNXtuchX7qzvvABQtLu4b1Mk54/7cyfJ6Q0fNe0qH4w/4Hab33r+p1WvV5unvP+\n0BzrKiHVxR31PPPyOSd2M6bqzU58AkJKQz6kCZMmHt07ft/tnqRV3zaEpMX3jen+dP4gDsJ8\nsxOfgJDSkA9pQ/x+6/W9jKl8NWHV0YSkxnxjhhePvnHtPS9H0f8Yc1h059F7VB33aPHDfzn3\noIqqI6/7YOcbt4yp7Ddh1U8JKQXNIUXR/WXGnNf6GumDm8bv032fI696K4pOKj71mxVFjT8+\nfp/uVR/7dkO84HvGjI1+e9yevY9ZUfxUr37t0Mqeh9UUn21YA1Shq4R0Q/zU4b42t39ozKBr\nCgPq/kD+9rKexXF9esuON2YXDivmElIKWkOKTjZmQGNzSPXjml45HfBim5DOavrgZxuj6CfG\nHP5Aj/yt8l/n//iDfYv37funaIcB6tBVQnq+PP4bn3DTs41Nt++MB1Yx5cdX9TFmaH0UvRQ/\ns7jk+VWfMmbODjf+kJ/H8p9/pjshpaBNSLfGh2uaQ/qOMYf8+LH7TzPmU9GzP4vvueORl6J7\n438Mb3jme/Eklha+de03bNSc4+P7Phb/gdf6x0vv/uEoYw7+wB6gEl0lpOiq4r9R/Sbe/F7+\nZjwHMy0q/MNm4m9JM40ZF996q4+p2mLfOMeYfTbGTzUOIaQUtAnp8fjwweaQphpzbfyufvLM\nq7dHr5nia6T/Pumk+NtS/lvXl4sDHLul8F2qW/wv4Rxj9t4URW/GCd1tD1CJLhNSdN/YsmJL\ne94SFefwx/j9B1XGXBZFBxrz71tixxrza/vGocXgoisJKQVtQnomPvxZc0gXGlN92+tNi5pD\nanKBMccXB5g/Yf7r+P1fo+hwY76Sv/PR++6rtQeoRNcJKX45etu5HynEtKwwh9z2/AePMObs\n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", + "text/plain": [ + "Plot with title “Distance”" + ] + }, + "metadata": { + "image/png": { + "height": 420, + "width": 420 + }, + "text/plain": { + "height": 420, + "width": 420 + } + }, + "output_type": "display_data" + } + ], + "source": [ + "myString <- \"Hello, World!\"\n", + "\n", + "\n", + "par(mfrow=c(1, 2)) # divide graph area in 2 columns\n", + "boxplot(cars$speed, main=\"Speed\", sub=paste(\"Outlier rows: \", boxplot.stats(cars$speed)$out)) # box plot for 'speed'\n", + "boxplot(cars$dist, main=\"Distance\", sub=paste(\"Outlier rows: \", boxplot.stats(cars$dist)$out)) # box plot for 'distance'" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "0.80689490068921" + ], + "text/latex": [ + "0.80689490068921" + ], + "text/markdown": [ + "0.80689490068921" + ], + "text/plain": [ + "[1] 0.8068949" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Call:\n", + "lm(formula = dist ~ speed, data = cars)\n", + "\n", + "Coefficients:\n", + "(Intercept) speed \n", + " -17.579 3.932 \n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "\n", + "Call:\n", + "lm(formula = dist ~ speed, data = cars)\n", + "\n", + "Residuals:\n", + " Min 1Q Median 3Q Max \n", + "-29.069 -9.525 -2.272 9.215 43.201 \n", + "\n", + "Coefficients:\n", + " Estimate Std. Error t value Pr(>|t|) \n", + "(Intercept) -17.5791 6.7584 -2.601 0.0123 * \n", + "speed 3.9324 0.4155 9.464 1.49e-12 ***\n", + "---\n", + "Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1\n", + "\n", + "Residual standard error: 15.38 on 48 degrees of freedom\n", + "Multiple R-squared: 0.6511,\tAdjusted R-squared: 0.6438 \n", + "F-statistic: 89.57 on 1 and 48 DF, p-value: 1.49e-12\n" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "ename": "ERROR", + "evalue": "Error in library(DAAG): there is no package called ‘DAAG’\n", + "output_type": "error", + "traceback": [ + "Error in library(DAAG): there is no package called ‘DAAG’\nTraceback:\n", + "1. library(DAAG)" + ] + } + ], + "source": [ + "cor(cars$speed, cars$dist) # calculate correlation between speed and distance \n", + "linearMod <- lm(dist ~ speed, data=cars) # build linear regression model on full data\n", + "print(linearMod)\n", + "summary(linearMod) # model summary\n" ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "R", + "language": "R", + "name": "ir" }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# xlrd, https://blogs.harvard.edu/rprasad/2014/06/16/reading-excel-with-python-xlrd/\n", - "import xlrd\n", - "\n", - "fname = os.path.join(TESTDIR, 'excel_example.xlsx')\n", - "\n", - "# Open the workbook\n", - "xl_workbook = xlrd.open_workbook(fname)" - ] + "language_info": { + "codemirror_mode": "r", + "file_extension": ".r", + "mimetype": "text/x-r-source", + "name": "R", + "pygments_lexer": "r", + "version": "3.6.2" } - ] + }, + "nbformat": 4, + "nbformat_minor": 4 } \ No newline at end of file diff --git a/tests/excel_example.xlsx b/tests/excel_example.xlsx deleted file mode 100644 index 384cf77..0000000 Binary files a/tests/excel_example.xlsx and /dev/null differ diff --git a/tests/test.sh b/tests/test.sh index 15ae526..5468ab4 100644 --- a/tests/test.sh +++ b/tests/test.sh @@ -1,17 +1,16 @@ #!/bin/bash DATASCIENCE_TESTDIR=$TESTDIR/datascience-notebook -jupyter nbconvert --to python "${DATASCIENCE_TESTDIR}/datascience_notebook.ipynb" - -if ! python3 $DATASCIENCE_TESTDIR/datascience_notebook.py; then +if ! jupyter nbconvert --execute "${DATASCIENCE_TESTDIR}/datascience_notebook.ipynb"; then + echo "Integration test failed" + echo "could not execute datascience_notebook" exit 1 fi -# test protobuf -protoc -I=$DATASCIENCE_TESTDIR --python_out=$DATASCIENCE_TESTDIR $DATASCIENCE_TESTDIR/addressbook.proto -python3 $DATASCIENCE_TESTDIR/addressbook_pb2.py +if ! test -f "${DATASCIENCE_TESTDIR}/datascience_notebook.html"; then + echo "Integration test failed" + echo "Compiled datascience_notebook.html does not exist" + exit 1 +fi -# test okpy -git clone https://github.com/okpy/ok-client.git -ok -q ok-client/demo/ok_test/q2 -rm -rf ok-client \ No newline at end of file +echo "datascience-notebook integration test passed!" \ No newline at end of file