summaryrefslogtreecommitdiff
path: root/macros/latex/contrib/jupynotex/example
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2020-10-21 03:02:35 +0000
committerNorbert Preining <norbert@preining.info>2020-10-21 03:02:35 +0000
commitd800950bfaacab9c6c4bf4d44206d60016b2f04d (patch)
treea47704a85d1a29b69fcede9454aa32ed2c7b5902 /macros/latex/contrib/jupynotex/example
parent6c4fb500d344400c932c5792f94aee3e34cbe6a7 (diff)
CTAN sync 202010210302
Diffstat (limited to 'macros/latex/contrib/jupynotex/example')
-rwxr-xr-xmacros/latex/contrib/jupynotex/example/build5
-rw-r--r--macros/latex/contrib/jupynotex/example/example.tex27
-rw-r--r--macros/latex/contrib/jupynotex/example/notebook.ipynb593
3 files changed, 625 insertions, 0 deletions
diff --git a/macros/latex/contrib/jupynotex/example/build b/macros/latex/contrib/jupynotex/example/build
new file mode 100755
index 0000000000..51598c0450
--- /dev/null
+++ b/macros/latex/contrib/jupynotex/example/build
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+ln -s ../jupynotex.py .
+ln -s ../jupynotex.sty .
+xelatex -shell-escape example.tex
diff --git a/macros/latex/contrib/jupynotex/example/example.tex b/macros/latex/contrib/jupynotex/example/example.tex
new file mode 100644
index 0000000000..5251586b63
--- /dev/null
+++ b/macros/latex/contrib/jupynotex/example/example.tex
@@ -0,0 +1,27 @@
+\documentclass{article}
+
+\usepackage{jupynotex}
+
+\begin{document}
+
+One cell:
+
+\jupynotex[1]{notebook.ipynb}
+
+
+A range of cells:
+
+\jupynotex[4-6]{notebook.ipynb}
+
+
+Some specific cells:
+
+\jupynotex[12,17]{notebook.ipynb}
+
+
+The whole notebook:
+
+\jupynotex{notebook.ipynb}
+
+
+\end{document}
diff --git a/macros/latex/contrib/jupynotex/example/notebook.ipynb b/macros/latex/contrib/jupynotex/example/notebook.ipynb
new file mode 100644
index 0000000000..a423e9a11f
--- /dev/null
+++ b/macros/latex/contrib/jupynotex/example/notebook.ipynb
@@ -0,0 +1,593 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "metadata": {
+ "scrolled": true
+ },
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Non alphanums {'>', '\\t', '[', '\\r', '\\x13', '\\x1d', '<', '^', '\\x04', '\\x08', '\\x17', '\\x1b', '\\\\', '\\x19', '`', '\\x1f', '$', '\\x0b', '\\x14', '\\x12', '\\x18', ')', '=', '?', ' ', \"'\", '\\x1c', '|', ';', '/', '\\x1e', '\\n', '\\x07', '\\x10', '\\x03', '\\x02', '#', '\\x0c', '@', '\\x16', '_', '}', '.', '-', '(', '!', '+', '\\x06', ']', '{', ':', '\\x01', '\\x11', '\\x0f', '\\x05', ',', '~', '\\x15', '\\x00', '\"', '%', '\\x0e', '*', '&', '\\x1a'}\n",
+ "Separators b'([\\\\>\\\\\\t\\\\[\\\\\\r\\\\\\x13\\\\\\x1d\\\\<\\\\^\\\\\\x04\\\\\\x08\\\\\\x17\\\\\\x1b\\\\\\\\\\\\\\x19\\\\`\\\\\\x1f\\\\$\\\\\\x0b\\\\\\x14\\\\\\x12\\\\\\x18\\\\)\\\\=\\\\?\\\\ \\\\\\'\\\\\\x1c\\\\|\\\\;\\\\/\\\\\\x1e\\\\\\n\\\\\\x07\\\\\\x10\\\\\\x03\\\\\\x02\\\\#\\\\\\x0c\\\\@\\\\\\x16\\\\_\\\\}\\\\.\\\\-\\\\(\\\\!\\\\+\\\\\\x06\\\\]\\\\{\\\\:\\\\\\x01\\\\\\x11\\\\\\x0f\\\\\\x05\\\\,\\\\~\\\\\\x15\\\\\\x00\\\\\"\\\\%\\\\\\x0e\\\\*\\\\&\\\\\\x1a])'\n"
+ ]
+ }
+ ],
+ "source": [
+ "import string\n",
+ "\n",
+ "non_alphanums = set(chr(x) for x in range(127)) - set(string.ascii_letters) - set(string.digits)\n",
+ "print(\"Non alphanums\", non_alphanums)\n",
+ "separators = '([' + ''.join('\\\\' + x for x in non_alphanums) + '])'\n",
+ "separators = separators.encode('ascii')\n",
+ "print(\"Separators\", separators)\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "[b'dlg',\n",
+ " b'=',\n",
+ " b'Resource',\n",
+ " b'.',\n",
+ " b'loadfromresfile',\n",
+ " b'(',\n",
+ " b'filename',\n",
+ " b',',\n",
+ " b'win',\n",
+ " b',',\n",
+ " b'QuoteDialog',\n",
+ " b'.',\n",
+ " b'MyQuoteDialog',\n",
+ " b',',\n",
+ " b\"'\",\n",
+ " b'QuoteDialog',\n",
+ " b\"'\",\n",
+ " b',',\n",
+ " b'win',\n",
+ " b')']"
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "import re\n",
+ "program_line = b\"\"\"dlg = Resource.loadfromresfile(filename, win, QuoteDialog.MyQuoteDialog, 'QuoteDialog', win)\"\"\"\n",
+ "tokens = [t for x in re.split(separators, program_line) if (t := x.strip())]\n",
+ "tokens"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Checking base dir: HTML document\n",
+ "Checking base dir: C source\n",
+ "Checking base dir: C++ source\n",
+ "Checking base dir: PHP script\n",
+ "Checking base dir: ReStructuredText file\n",
+ "Checking base dir: Python script\n",
+ "Checking base dir: Ruby script\n",
+ "Checking base dir: Java source\n",
+ "Checking base dir: Objective-C source\n",
+ "Checking base dir: Perl5 module source\n",
+ "Checking base dir: XML 1.0 document\n"
+ ]
+ }
+ ],
+ "source": [
+ "import os\n",
+ "from collections import Counter\n",
+ "\n",
+ "DUMP_BASE = '/home/facundo/devel/ml/dump'\n",
+ "\n",
+ "# directories with 1000 files of each code type, excluding \"just text\" (ascii, utf8, etc)\n",
+ "CODE_TYPES = [\n",
+ " 'HTML document',\n",
+ " 'C source',\n",
+ " 'C++ source',\n",
+ " 'PHP script',\n",
+ " 'ReStructuredText file',\n",
+ " 'Python script',\n",
+ " 'Ruby script',\n",
+ " 'Java source',\n",
+ " 'Objective-C source',\n",
+ " 'Perl5 module source',\n",
+ " 'XML 1.0 document',\n",
+ "]\n",
+ "\n",
+ "# let's collect ALL tokens present in all the program files\n",
+ "tokens = Counter()\n",
+ "for basedir in CODE_TYPES:\n",
+ " print(\"Checking base dir:\", basedir)\n",
+ " for dirpath, dirnames, filenames in os.walk(os.path.join(DUMP_BASE, basedir)):\n",
+ " for fname in filenames:\n",
+ " fpath = os.path.join(dirpath, fname)\n",
+ " with open(fpath, 'rb') as fh:\n",
+ " tokens.update(t for x in re.split(separators, fh.read()) if (t := x.strip()))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Total different tokens 213400\n",
+ " 870525 b'.'\n",
+ " 756849 b'_'\n",
+ " 730609 b'('\n",
+ " 717433 b'='\n",
+ " 699725 b')'\n",
+ " 688556 b'/'\n",
+ " 661461 b'\"'\n",
+ " 640989 b','\n",
+ " 625121 b'-'\n",
+ " 594091 b'>'\n"
+ ]
+ }
+ ],
+ "source": [
+ "different_tokens = len(tokens)\n",
+ "print(\"Total different tokens\", different_tokens)\n",
+ "for name, quant in tokens.most_common(10):\n",
+ " print(\"{:8d} {}\".format(quant, name))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Total tokens found: 20101050\n",
+ "Total representative: 3324\n",
+ "Last ten...\n",
+ " 289 b'enables'\n",
+ " 289 b'smaller'\n",
+ " 289 b'Creates'\n",
+ " 289 b'cross'\n",
+ " 289 b'GLFW'\n",
+ " 289 b'Os'\n",
+ " 289 b'usb'\n",
+ " 288 b'stylesheets'\n",
+ " 288 b'ad'\n",
+ " 288 b'WIDTH'\n"
+ ]
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "\n",
+ "total_tokens = sum(tokens.values())\n",
+ "print(\"Total tokens found:\", total_tokens)\n",
+ "\n",
+ "most = total_tokens * 0.9\n",
+ "tot = 0\n",
+ "representative_data = []\n",
+ "for name, quant in tokens.most_common():\n",
+ " representative_data.append((name, quant))\n",
+ " tot += quant\n",
+ " if tot > most:\n",
+ " break\n",
+ "\n",
+ "print(\"Total representative:\", len(representative_data))\n",
+ "print(\"Last ten...\")\n",
+ "for name, quant in representative_data[-10:]:\n",
+ " print(\"{:8d} {}\".format(quant, name))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Collecting data from base dir HTML document\n",
+ "Collecting data from base dir C source\n",
+ "Collecting data from base dir C++ source\n",
+ "Collecting data from base dir PHP script\n",
+ "Collecting data from base dir ReStructuredText file\n",
+ "Collecting data from base dir Python script\n",
+ "Collecting data from base dir Ruby script\n",
+ "Collecting data from base dir Java source\n",
+ "Collecting data from base dir Objective-C source\n",
+ "Collecting data from base dir Perl5 module source\n",
+ "Collecting data from base dir XML 1.0 document\n",
+ "Src data samples: 11000\n"
+ ]
+ }
+ ],
+ "source": [
+ "import random\n",
+ "\n",
+ "# real \"ML data\": a list of (code_type, features) (one pair for each file)\n",
+ "# code_type: the *position* of the code type corresponding to the file (needs to be an int)\n",
+ "# features: a list of values, each value corresponds to how many of the tokens of that position the file has\n",
+ "\n",
+ "representative_tokens = [name for name, _ in representative_data]\n",
+ "\n",
+ "all_src_data = []\n",
+ "for idx, basedir in enumerate(CODE_TYPES):\n",
+ " print(\"Collecting data from base dir\", basedir)\n",
+ " for dirpath, dirnames, filenames in os.walk(os.path.join(DUMP_BASE, basedir)):\n",
+ " for fname in filenames:\n",
+ " fpath = os.path.join(dirpath, fname)\n",
+ " with open(fpath, 'rb') as fh:\n",
+ " fcontent = fh.read()\n",
+ " \n",
+ " file_tokens = Counter(t for x in re.split(separators, fcontent) if (t := x.strip()))\n",
+ " token_quantities = [file_tokens.get(t, 0) for t in representative_tokens]\n",
+ "\n",
+ " all_src_data.append((idx, token_quantities))\n",
+ "\n",
+ "print(\"Src data samples:\", len(all_src_data))\n",
+ "\n",
+ "# shuffle, as currently is too much \"per directory\"\n",
+ "random.shuffle(all_src_data)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import tensorflow as tf\n",
+ "from tensorflow.keras import Model, layers\n",
+ "import numpy as np\n",
+ "\n",
+ "# representation of our model\n",
+ "num_classes = len(CODE_TYPES)\n",
+ "num_features = len(representative_tokens)\n",
+ "\n",
+ "# 1st and 2nd layer number of neurons (these numbers are just chamuyo)\n",
+ "n_hidden_1 = 128 \n",
+ "n_hidden_2 = 256\n",
+ "\n",
+ "# training parameters (more chamuyo)\n",
+ "learning_rate = 0.1\n",
+ "\n",
+ "batch_size = 256\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Example code types: (2, 0, 4, 4, 5)\n",
+ "Example token quants: [246, 786, 246, 61, 246] [32, 32, 5, 22, 5]\n",
+ "Token quants shape: (11000, 3324)\n",
+ "Example normalized quants: [0.3129771 1. 0.3129771 0.07760815 0.3129771 ] [0.9411765 0.9411765 0.14705883 0.64705884 0.14705883]\n"
+ ]
+ }
+ ],
+ "source": [
+ "# separate the source data and into two pairing lists\n",
+ "code_types, token_quantities = zip(*all_src_data)\n",
+ "print(\"Example code types:\", code_types[:5])\n",
+ "print(\"Example token quants:\", token_quantities[0][:5], token_quantities[117][:5])\n",
+ "\n",
+ "# convert features to float\n",
+ "float_quantities = np.array(token_quantities, np.float32)\n",
+ "print(\"Token quants shape:\", float_quantities.shape)\n",
+ "\n",
+ "# normalize EACH ONE to [0, 1]\n",
+ "for quants in float_quantities:\n",
+ " quants /= max(quants)\n",
+ "print(\"Example normalized quants:\", float_quantities[0][:5], float_quantities[117][:5])"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Real training set: 89.70%\n"
+ ]
+ }
+ ],
+ "source": [
+ "# let's prepare the teching sets, input and output for training first (90% of cases) and\n",
+ "# then testing what's learned (the remaining 10%); \n",
+ "input_training = []\n",
+ "output_training = []\n",
+ "input_testing = []\n",
+ "output_testing = []\n",
+ "for token_distribution, code_type in zip(float_quantities, code_types):\n",
+ " if random.random() < .1:\n",
+ " input_testing.append(token_distribution)\n",
+ " output_testing.append(code_type)\n",
+ " else:\n",
+ " input_training.append(token_distribution)\n",
+ " output_training.append(code_type)\n",
+ "print(\"Real training set: {:.2f}%\".format(100 * len(input_training) / len(float_quantities))) "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import tensorflow as tf\n",
+ "from tensorflow.keras import Model\n",
+ "\n",
+ "class NeuralNet(Model):\n",
+ " \"\"\"Chamuyo al cuadrado.\"\"\"\n",
+ " \n",
+ " def __init__(self):\n",
+ " super(NeuralNet, self).__init__()\n",
+ " self.fc1 = layers.Dense(n_hidden_1, activation=tf.nn.sigmoid) # se puede cambiar a relu\n",
+ " self.fc2 = layers.Dense(n_hidden_2, activation=tf.nn.sigmoid) # se puede cambiar a relu\n",
+ " self.out = layers.Dense(num_classes)\n",
+ "\n",
+ " def call(self, x, is_training=False):\n",
+ " x = self.fc1(x)\n",
+ " x = self.fc2(x)\n",
+ " x = self.out(x)\n",
+ " if not is_training:\n",
+ " # tf cross entropy expect logits without softmax, so only\n",
+ " # apply softmax when not training.\n",
+ " x = tf.nn.softmax(x)\n",
+ " return x\n",
+ "\n",
+ "neural_net = NeuralNet()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 11,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Note that this will apply 'softmax' to the logits.\n",
+ "def cross_entropy_loss(x, y):\n",
+ " # Convert labels to int 64 for tf cross-entropy function.\n",
+ " y = tf.cast(y, tf.int64)\n",
+ " # Apply softmax to logits and compute cross-entropy.\n",
+ " loss = tf.nn.sparse_softmax_cross_entropy_with_logits(labels=y, logits=x)\n",
+ " # Average loss across the batch.\n",
+ " return tf.reduce_mean(loss)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 12,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Stochastic gradient descent optimizer.\n",
+ "optimizer = tf.optimizers.SGD(learning_rate)\n",
+ "\n",
+ "# Optimización. \n",
+ "def run_optimization(x, y):\n",
+ " # Funciones para calcular el gradiente\n",
+ " with tf.GradientTape() as g:\n",
+ " # Algoritmo de forward\n",
+ " pred = neural_net(x, is_training=True)\n",
+ " # Computa la función de costo o pérdida utilizando entropía cruzada\n",
+ " loss = cross_entropy_loss(pred, y)\n",
+ " \n",
+ " # Actualiza las variables de entrenamiento.\n",
+ " trainable_variables = neural_net.trainable_variables\n",
+ "\n",
+ " # Computa los gradientes\n",
+ " gradients = g.gradient(loss, trainable_variables)\n",
+ " \n",
+ " # Actualiza los nuevos parámetros W (pesos) y b (bias).\n",
+ " optimizer.apply_gradients(zip(gradients, trainable_variables))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 13,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Accuracy metric.\n",
+ "def accuracy(y_pred, y_true):\n",
+ " # Predicted class is the index of highest score in prediction vector (i.e. argmax).\n",
+ " correct_prediction = tf.equal(tf.argmax(y_pred, 1), tf.cast(y_true, tf.int64))\n",
+ " return tf.reduce_mean(tf.cast(correct_prediction, tf.float32), axis=-1)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Step 100: loss=2.379924, accuracy=0.125000\n",
+ "Step 200: loss=2.372284, accuracy=0.128906\n",
+ "Step 300: loss=2.366430, accuracy=0.140625\n",
+ "Step 400: loss=2.360768, accuracy=0.121094\n",
+ "Step 500: loss=2.347394, accuracy=0.109375\n",
+ "Step 600: loss=2.349795, accuracy=0.199219\n",
+ "Step 700: loss=2.312950, accuracy=0.218750\n",
+ "Step 800: loss=2.284508, accuracy=0.289062\n",
+ "Step 900: loss=2.173395, accuracy=0.335938\n",
+ "Step 1000: loss=2.115966, accuracy=0.300781\n",
+ "Step 1100: loss=1.977837, accuracy=0.441406\n",
+ "Step 1200: loss=1.860783, accuracy=0.425781\n",
+ "Step 1300: loss=1.866206, accuracy=0.425781\n",
+ "Step 1400: loss=1.773057, accuracy=0.402344\n",
+ "Step 1500: loss=1.736271, accuracy=0.546875\n",
+ "Step 1600: loss=1.626320, accuracy=0.578125\n",
+ "Step 1700: loss=1.537970, accuracy=0.539062\n",
+ "Step 1800: loss=1.369012, accuracy=0.609375\n",
+ "Step 1900: loss=1.286771, accuracy=0.625000\n",
+ "Step 2000: loss=1.270916, accuracy=0.640625\n"
+ ]
+ }
+ ],
+ "source": [
+ "train_data = tf.data.Dataset.from_tensor_slices((input_training, output_training))\n",
+ "\n",
+ "# NOTE: this doesn't only selectes, it completely transform the structures\n",
+ "# from <TensorSliceDataset shapes: ((3324,), ()), types: (tf.float32, tf.int32)>\n",
+ "# to <PrefetchDataset shapes: ((None, 3324), (None,)), types: (tf.float32, tf.int32)>\n",
+ "train_data = train_data.repeat().shuffle(5000).batch(batch_size).prefetch(1)\n",
+ "\n",
+ "display_step = 100\n",
+ "training_steps = 2000\n",
+ "\n",
+ "# Run training for the given number of steps.\n",
+ "for step, (input_batch, output_batch) in enumerate(train_data.take(training_steps), 1):\n",
+ " # Run the optimization to update W and b values.\n",
+ " run_optimization(input_batch, output_batch)\n",
+ " \n",
+ " if step % display_step == 0:\n",
+ " pred = neural_net(input_batch, is_training=True)\n",
+ " loss = cross_entropy_loss(pred, output_batch)\n",
+ " acc = accuracy(pred, output_batch)\n",
+ " print(\"Step {}: loss={:f}, accuracy={:f}\".format(step, loss, acc))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Test Accuracy: 0.578994\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Test model on validation set\n",
+ "input_testing = np.array(input_testing)\n",
+ "pred = neural_net(input_testing, is_training=False)\n",
+ "print(\"Test Accuracy: {:f}\".format(accuracy(pred, output_testing)))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 18,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# If we consider the \"more probable\" prediction on each item, how well it goes?\n",
+ "all_guesses = neural_net(input_testing, is_training=False)\n",
+ "guesses_ok = [0] * len(CODE_TYPES)\n",
+ "guesses_bad = [0] * len(CODE_TYPES)\n",
+ "\n",
+ "for guess, real in zip(all_guesses, output_testing):\n",
+ " # guess is an array of len(CODE_TYPES) with a float in each position showing\n",
+ " # which one is the most probable to be real, so we need to get position \n",
+ " # for the max one and check if it matches with the real real :)\n",
+ " position_for_max = np.where(guess == np.amax(guess))[0][0]\n",
+ " if position_for_max == real:\n",
+ " guesses_ok[real] += 1\n",
+ " else:\n",
+ " guesses_bad[real] += 1"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 19,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "image/png": "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c0lNnjyZrl27cswxxxT3DRs2jG222YaTTz6ZwYMHM2TIECZODL93pkyZwrbbbsuee+7JxhtvvNT2kiVLOPnkkxk5ciRDhw5l/PiG9X3OOecchgwZwrBhwzjttNO48cYbmTZtGgcffDDDhw/nk08+YdKkSWy66aYMGTKEI444gk8//TT318S1H3UpKUg6DNgD2NEa5u5+jcaLcfejZYupO5eLp556is0333yp/TfddBMzZszgiSee4O2332bkyJFst912ADz22GM89dRTDBw4kClTpjTavuSSS1hllVWYOnUqn376KVtvvTW77LILzz77LLfeeiuPPPIIK664Iu+88w6rrbYaF154Ib/97W8ZMWIEixYt4rDDDmPSpElsuOGGHHrooVx88cWMGzcu51fFtRe5lxQk7QacAuxpZh9nDt0GHChpBUkDCasrPZp3+pxbVvfffz8HHXQQnTt3pm/fvmy//fZMnToVgFGjRjUaB5DdvvPOO7nqqqsYPnw4W2yxBQsWLOD555/n7rvv5vDDD2fFFVcEYLXVVlsq5uzZsxk4cGCx2mrs2LHce++9qS/VtWNJSwqSriUsPL6GpLnAGYTeRisAd8WucA+b2TFmNkvS9YRFtxcDx5vZkpTpc25ZbLLJJtx4440tes5KK61UcdvMuOCCC9h118a9sO+4445lT6RzyyhpScHMDjKzNc2sq5n1M7PLzGx9M1vHzIbH2zGZx//KzNYzs0Fm9s+UaXNuWY0ZM4ZPP/2USy65pLhv5syZ9O7dm4kTJ7JkyRLmz5/Pvffey6hRo5o936677srFF1/M559/DsBzzz3HRx99xM4778wVV1zBxx+HAvU777wDQM+ePVm4cCEAgwYNYs6cObzwwgsA/PnPf2b77X0te7fs6t37yLnlUm0X0lqSxM0338y4ceM455xz6N69OwMGDOC8887jww8/ZNiwYUjiN7/5DV/60pd49tlnmzzfkUceyZw5c9hss80wM/r06cMtt9zCbrvtxowZMxgxYgTdunVj991359e//jWHHXYYxxxzDD169OChhx7iiiuuYL/99mPx4sWMHDmyUQO4cy3V5tdoHjFihPkiOx3HM888w1e+8pV6J6Pdqdvr6l1S60bSdDMbUbq/Y5cUUr0h/c3onGujfEI855xzRZ4pOOecK/JMwTnnXJFnCs4554o8U3DOOVfUsXsfubav1j3Iqug5tvLKK/Phhx/WNm4NnXfeeRx99NHF6TGcawkvKTjXiixevLjJ7Wqcd955xVHQzrWUZwrOLaMpU6YwevRo9t13XzbaaCMOPvhgCoNBp06dyle/+lWGDRvGqFGjWLhwIYsWLeLwww9nyJAhbLrppkyePBkI6yPsueeejBkzhh133HGp7Y8++ogjjjiCUaNGsemmm3LrrbcCsGTJEn74wx8yePBghg4dygUXXMD555/P66+/zg477MAOO+wAwLXXXsuQIUMYPHgwp556an1eLNdmePWRc8vh8ccfZ9asWay11lpsvfXWPPDAA4waNYoDDjiAiRMnMnLkSD744AN69OjBH/7wByTx5JNP8uyzz7LLLrvw3HPPAWFq7ZkzZ7LaaqsxYcKERts/+tGPGDNmDJdffjnvvfceo0aNYqedduKqq65izpw5zJgxgy5duhSn1j733HOZPHkya6yxBq+//jqnnnoq06dPZ9VVV2WXXXbhlltuYe+9967vC+daLS8pOLccRo0aRb9+/ejUqRPDhw9nzpw5zJ49mzXXXJORI0cC0KtXL7p06cL999/PIYccAsBGG23EuuuuW8wUdt5550ZTY2e377zzTs4++2yGDx/O6NGjWbRoEa+88gp333033/3ud4srt5WbWnvq1KmMHj2aPn360KVLFw4++GCfWts1yUsKzi2HFVZYoXi/c+fOy9QGAM1Prf3Xv/6VQYMGLVsinWsBLyk4V2ODBg1i3rx5xQV2Fi5cyOLFi9l222255pqw+uxzzz3HK6+8UtUX/a677soFF1xQbK94/PHHgVCaGD9+fDEjKje19qhRo7jnnnt4++23WbJkCddee61Pre2a5CUF17a1wskHu3XrxsSJEznhhBP45JNP6NGjB3fffTfHHXccxx57LEOGDKFLly5MmDChUUmjkp/+9KeMGzeOoUOH8sUXXzBw4EBuv/12jjzySJ577jmGDh1K165dOeqoo/je977H0UcfzW677cZaa63F5MmTOfvss9lhhx0wM77+9a+z11575fAquLaqY0+d7bOktjk+dXYaPnV2x1Np6myvPnLOOVfk1UeudvxXn3NtnpcUXJvT1qs8Wxt/PV2WZwquTenevTsLFizwL7IaMTMWLFhA9+7d650U10p49ZFrU/r168fcuXOZP39+vZPSbnTv3p1+/frV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\n",
+ "text/plain": [
+ "<Figure size 432x288 with 1 Axes>"
+ ]
+ },
+ "metadata": {
+ "needs_background": "light"
+ },
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "import numpy as np\n",
+ "import matplotlib.pyplot as plt\n",
+ "\n",
+ "values_ok = [guesses_ok[i] for i in range(len(CODE_TYPES))]\n",
+ "values_bad = [guesses_bad[i] for i in range(len(CODE_TYPES))]\n",
+ "x_positions = np.arange(len(CODE_TYPES))\n",
+ "\n",
+ "p1 = plt.bar(x_positions, guesses_ok)\n",
+ "p2 = plt.bar(x_positions, guesses_bad, bottom=guesses_ok)\n",
+ "\n",
+ "plt.ylabel('Tipo de código')\n",
+ "plt.title('Aciertos o no, por tipo de código')\n",
+ "plt.xticks(x_positions, CODE_TYPES, rotation=90)\n",
+ "#plt.yticks(np.arange(0, 81, 10))\n",
+ "plt.legend((p1[0], p2[0]), ('Correcto', 'Incorrecto'))\n",
+ "\n",
+ "plt.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": []
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "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.2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}