{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "#### New to Plotly?\n", "Plotly's Python library is free and open source! [Get started](https://plot.ly/python/getting-started/) by downloading the client and [reading the primer](https://plot.ly/python/getting-started/).\n", "
You can set up Plotly to work in [online](https://plot.ly/python/getting-started/#initialization-for-online-plotting) or [offline](https://plot.ly/python/getting-started/#initialization-for-offline-plotting) mode, or in [jupyter notebooks](https://plot.ly/python/getting-started/#start-plotting-online).\n", "
We also have a quick-reference [cheatsheet](https://images.plot.ly/plotly-documentation/images/python_cheat_sheet.pdf) (new!) to help you get started!\n", "#### Version Check\n", "Plotly's python package is updated frequently. Run `pip install plotly --upgrade` to use the latest version." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "'2.0.1'" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import plotly\n", "plotly.__version__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Imports\n", "Salesforce reports are great for getting a handle on the numbers but [Plotly](https://plot.ly/) allows for interactivity not built into the Reports Module in Salesforce. Luckily Salesforce has amazing tools around exporting data, from excel and csv files to a robust and reliable API. With [Simple Salesforce](https://github.com/neworganizing/simple-salesforce), it's simple to make REST calls to the Salesforce API and get your hands on data to make real time, interactive charts.\n", "\n", "This notebook walks you through that basic process of getting something like that set up. \n", "First you'll need [Plotly](https://plot.ly/). Plotly is a free web-based platform for making graphs. You can keep graphs private, make them public, and run Plotly on your own servers (https://plot.ly/product/enterprise/). To get started visit https://plot.ly/python/getting-started/ . It's simple interface makes it easy to get interactive graphics done quickly.\n", "\n", "You'll also need a Salesforce Developer (or regular Salesforce Account). [You can get a salesforce developer account for free](https://developer.salesforce.com/signup) at their developer portal." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import plotly.plotly as py\n", "import plotly.graph_objs as go\n", "\n", "import pandas as pd\n", "import numpy as np\n", "from collections import Counter\n", "import requests\n", "\n", "from simple_salesforce import Salesforce\n", "requests.packages.urllib3.disable_warnings() # this squashes insecure SSL warnings - DO NOT DO THIS ON PRODUCTION!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Log In to Salesforce\n", "I've stored my Salesforce login in a text file however you're free to store them as environmental variables. As a reminder, login details should NEVER be included in version control. Logging into Salesforce is as easy as entering in your username, password, and security token given to you by Salesforce. [Here's how to get your security token from Salesforce.](https://help.salesforce.com/apex/HTViewHelpDoc?id=user_security_token.htm)" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": true }, "outputs": [], "source": [ "with open('salesforce_login.txt') as f:\n", " username, password, token = [x.strip(\"\\n\") for x in f.readlines()]\n", "sf = Salesforce(username=username, password=password, security_token=token)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### SOQL Queries\n", "At this time we're going to write a simple SOQL query to get some basic information from some leads. We'll query the status and Owner from our leads. Further reference for the Salesforce API and writing SOQL queries: http://www.salesforce.com/us/developer/docs/soql_sosl/ SOQL is just Salesforce's version of SQL." ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": true }, "outputs": [], "source": [ "leads_for_status = sf.query(\"SELECT Id, Status, Owner.Name FROM Lead\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we'll use a quick list comprehension to get just our statuses from those records (which are in an ordered dictionary format)." ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": true }, "outputs": [], "source": [ "statuses = [x['Status'] for x in leads_for_status[\"records\"]]\n", "status_counts = Counter(statuses)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can take advantage of Plotly's simple IPython Notebook interface to plot the graph in our notebook." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data = [go.Bar(x=status_counts.keys(), y=status_counts.values())]\n", "py.iplot(data, filename='salesforce/lead-distributions')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While this graph gives us a great overview what status our leads are in, we'll likely want to know how each of the sales representatives are doing with their own leads. For that we'll need to get the owners using a similar list comprehension as we did above for the status." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": true }, "outputs": [], "source": [ "owners = [x['Owner']['Name'] for x in leads_for_status[\"records\"]]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For simplicity in grouping the values, I'm going to plug them into a pandas DataFrame." ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": true }, "outputs": [], "source": [ "df = pd.DataFrame({'Owners':owners, 'Status':statuses})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that we've got that we can do a simple lead comparison to compare how our Sales Reps are doing with their leads. We just create the bars for each lead owner." ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": true }, "outputs": [], "source": [ "lead_comparison = []\n", "for name, vals in df.groupby('Owners'):\n", " counts = vals.Status.value_counts()\n", " lead_comparison.append(Bar(x=counts.index, y=counts.values, name=name))" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "py.iplot(lead_comparison, filename='salesforce/lead-owner-status-groupings')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What's great is that plotly makes it simple to compare across groups. However now that we've seen leads, it's worth it to look into Opportunities." ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": true }, "outputs": [], "source": [ "opportunity_amounts = sf.query(\"SELECT Id, Probability, StageName, Amount, Owner.Name FROM Opportunity WHERE AMOUNT < 10000\")" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": true }, "outputs": [], "source": [ "amounts = [x['Amount'] for x in opportunity_amounts['records']]\n", "owners = [x['Owner']['Name'] for x in opportunity_amounts['records']]" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": true }, "outputs": [], "source": [ "hist1 = go.Histogram(x=amounts)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "py.iplot([hist1], filename='salesforce/opportunity-probability-histogram')" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": true }, "outputs": [], "source": [ "df2 = pd.DataFrame({'Amounts':amounts,'Owners':owners})" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": true }, "outputs": [], "source": [ "opportunity_comparisons = []\n", "for name, vals in df2.groupby('Owners'):\n", " temp = Histogram(x=vals['Amounts'], opacity=0.75, name=name)\n", " opportunity_comparisons.append(temp)" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": true }, "outputs": [], "source": [ "layout = go.Layout(\n", " barmode='stack'\n", ")\n", "fig = go.Figure(data=opportunity_comparisons, layout=layout)" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "py.iplot(fig, filename='salesforce/opportunities-histogram')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "By clicking on the \"play with this data!\" you can export, share, collaborate, and embed these plots. I've used it to share annotations about data and try out more colors. The GUI makes it easy for less technically oriented people to play with the data as well. Check out how the above was changed below or you can follow the link to make your own edits." ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", " \"Chuck\n", " \n", "
" ], "text/plain": [ "" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "from IPython.display import HTML\n", "HTML(\"\"\"
\n", " \"Chuck\n", " \n", "
\"\"\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "After comparing those two representatives. It's always helpful to have that high level view of the sales pipeline. Below I'm querying all of our open opportunities with their Probabilities and close dates. This will help us make a forecasting graph of what's to come soon." ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": true }, "outputs": [], "source": [ "large_opps = sf.query(\"SELECT Id, Name, Probability, ExpectedRevenue, StageName, Amount, CloseDate, Owner.Name FROM Opportunity WHERE StageName NOT IN ('Closed Lost', 'Closed Won') AND Amount > 5000\")" ] }, { "cell_type": "code", "execution_count": 20, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
AmountCloseDateExpectedRevenueIdNameOwnerProbabilityStageName
0 15000 2015-06-03 9000 0061a000002vYrwAAE Grand Hotels Kitchen Generator Bill C 60 Id. Decision Makers
1 90000 2015-05-03 81000 0061a000002vYsIAAU Grand Hotels SLA Chuck Brockerson 90 Negotiation/Review
2 80000 2015-05-22 60000 0061a000002vYs3AAE Express Logistics Portable Truck Generators Bill C 75 Proposal/Price Quote
3 22000 2015-05-07 11000 0061a000002vYruAAE Express Logistics Standby Generator Chuck Brockerson 50 Value Proposition
4 100000 2015-06-17 90000 0061a000002vYsCAAU University of AZ Installations Bill C 90 Negotiation/Review
\n", "
" ], "text/plain": [ " Amount CloseDate ExpectedRevenue Id \\\n", "0 15000 2015-06-03 9000 0061a000002vYrwAAE \n", "1 90000 2015-05-03 81000 0061a000002vYsIAAU \n", "2 80000 2015-05-22 60000 0061a000002vYs3AAE \n", "3 22000 2015-05-07 11000 0061a000002vYruAAE \n", "4 100000 2015-06-17 90000 0061a000002vYsCAAU \n", "\n", " Name Owner Probability \\\n", "0 Grand Hotels Kitchen Generator Bill C 60 \n", "1 Grand Hotels SLA Chuck Brockerson 90 \n", "2 Express Logistics Portable Truck Generators Bill C 75 \n", "3 Express Logistics Standby Generator Chuck Brockerson 50 \n", "4 University of AZ Installations Bill C 90 \n", "\n", " StageName \n", "0 Id. Decision Makers \n", "1 Negotiation/Review \n", "2 Proposal/Price Quote \n", "3 Value Proposition \n", "4 Negotiation/Review " ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "large_opps_df = pd.DataFrame(large_opps['records'])\n", "large_opps_df['Owner'] = large_opps_df.Owner.apply(lambda x: x['Name']) # just extract owner name\n", "large_opps_df.drop('attributes', inplace=True, axis=1) # get rid of extra return data from Salesforce\n", "large_opps_df.head()" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": true }, "outputs": [], "source": [ "scatters = []\n", "for name, temp_df in large_opps_df.groupby('Owner'):\n", " hover_text = temp_df.Name + \"
Close Probability: \" + temp_df.Probability.map(str) + \"
Stage:\" + temp_df.StageName\n", " scatters.append(\n", " go.Scatter(\n", " x=temp_df.CloseDate,\n", " y=temp_df.Amount,\n", " mode='markers',\n", " name=name,\n", " text=hover_text,\n", " marker=dict(\n", " size=(temp_df.Probability / 2) # helps keep the bubbles of managable size\n", " )\n", " )\n", " )" ] }, { "cell_type": "code", "execution_count": 22, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "data = scatters\n", "layout = go.Layout(\n", " title='Open Large Deals',\n", " xaxis=dict(\n", " title='Close Date'\n", " ),\n", " yaxis=dict(\n", " title='Deal Amount',\n", " showgrid=False\n", " )\n", ")\n", "fig = go.Figure(data=data, layout=layout)\n", "py.iplot(fig, filename='salesforce/open-large-deals-scatter')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Plotly makes it easy to create many different kinds of charts. The above graph shows the deals in the pipeline over the coming months. The larger the bubble, the more likely it is to close. Hover over the bubbles to see that data. This graph is ideal for a sales manager to see how each of his sales reps are doing over the coming months.\n", "\n", "One of the benefits of Plotly is the availability of features. \n", "\n", "#### References\n", "\n", "- [Live update Plotly graphs in Python with cron jobs](http://moderndata.plot.ly/update-plotly-charts-with-cron-jobs-and-python/)\n", "- [Graph mysql data with Plotly and Python](http://moderndata.plot.ly/graph-data-from-mysql-database-in-python/)\n", "- [More on creating web-based visualizations in Python with Plotly](https://plot.ly/python/)" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "" ], "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" }, { "name": "stdout", "output_type": "stream", "text": [ "Collecting git+https://github.com/plotly/publisher.git\n", " Cloning https://github.com/plotly/publisher.git to c:\\users\\brand\\appdata\\local\\temp\\pip-req-build-7acqmmim\n", "Installing collected packages: publisher\n", " Found existing installation: publisher 0.11\n", " Uninstalling publisher-0.11:\n", " Successfully uninstalled publisher-0.11\n", " Running setup.py install for publisher: started\n", " Running setup.py install for publisher: finished with status 'done'\n", "Successfully installed publisher-0.11\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ "C:\\Python27\\lib\\site-packages\\IPython\\nbconvert.py:13: ShimWarning: The `IPython.nbconvert` package has been deprecated since IPython 4.0. You should import from nbconvert instead.\n", " \"You should import from nbconvert instead.\", ShimWarning)\n", "C:\\Python27\\lib\\site-packages\\publisher\\publisher.py:53: UserWarning: Did you \"Save\" this notebook before running this command? Remember to save, always save.\n", " warnings.warn('Did you \"Save\" this notebook before running this command? '\n" ] } ], "source": [ "from IPython.display import display, HTML\n", "\n", "display(HTML(''))\n", "display(HTML(''))\n", "\n", "! pip install git+https://github.com/plotly/publisher.git --upgrade\n", "import publisher\n", "publisher.publish(\n", " 'salesforce.ipynb', 'python/salesforce/', 'Plot Data from Salesforce', \n", " 'Create interactive graphs with salesforce, IPython Notebooks and Plotly',\n", " title='Interactive Salesforce Graphing | Plotly',\n", " redirect_from='ipython-notebooks/salesforce/', has_thumbnail='false', language='python', page_type='example_index',\n", " display_as='databases', order=4, ipynb= '~notebook_demo/1')" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.14" } }, "nbformat": 4, "nbformat_minor": 1 }