<p dir="auto">While the <code class="notranslate">hist</code> method allows to plot empirical cumulative distribution functions (ECDFs) with the <code class="notranslate">cumulative</code> and <code class="notranslate">density</code> keywords, this is far from perfect:</p>
<ul dir="auto">
<li>It uses a binning, which is unnecessary for ECDFs and either leads to inaccuracies (low binning) or a waste of time and spurious plot points for small datasets (high binning).</li>
<li>The default plot style is bars which are not the typical way to visualise ECDFs.</li>
<li>It is not easy to find. I bet that this very issue will soon be the first thing people wanting to plot the ECDF with Matplotlib will find.</li>
</ul>
<p dir="auto">Please implement a “proper” ECDF.</p>
<h3 dir="auto">Half a pull request</h3>
<p dir="auto">The following should contain all the major components necessary to implement this. If I had the the time and patience to work through Matplotlib’s code structure, testing, replicating everything for the legacy interface and so on, I would make a pull request.</p>
<div class="highlight highlight-source-python notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="from warnings import warn
import numpy as np
from matplotlib import pyplot as plt

def ecdf_points(data,remove_redundant,absolute=False):
    assert data.ndim==1
    assert np.all(np.isfinite(data))
    
    abscissae = np.append(data,np.inf)
    abscissae.sort()
    
    if absolute:
        ordinates = np.arange(len(abscissae),dtype=int)
    else:
        ordinates = np.linspace(0,1,len(abscissae)) 
    
    if remove_redundant:
        needed = np.ediff1d(abscissae,to_begin=True).astype(bool)
        abscissae = abscissae[needed]
        ordinates = ordinates[needed]
    
    return abscissae, ordinates

test_data = np.array([4,2,3,2,4,3,1,2,1,4])
np.testing.assert_allclose(
    ecdf_points(test_data,remove_redundant=True),
    [ [1,2,3,4,np.inf], [0,0.2,0.5,0.7,1.0] ],
)
np.testing.assert_equal(
    ecdf_points(test_data,remove_redundant=True,absolute=True),
    [ [1,2,3,4,np.inf], [0,2,5,7,10] ],
)


def ecdf(axes,x,*args,remove_redundant=False,absolute=False,**kwargs):
    &quot;&quot;&quot;
        Plot the empirical cumulative distribution function, i.e., the fraction of data points less than or equal than some value. Returns the supporting points of the underlying step plot.

        Parameters
        ----------
        x : one-dimensional array
            Input values
        
        remove_redundant : bool, optional
            Whether spurious points arising from identical data points shall be removed. This is mainly useful for large datasets containing many identical data points, where you get an unnecessarily large or inefficient figure otherwise. If your data does not contain any duplicate points, this has no effect and just uses some time and memory.
        
        absolute : bool, optional
            Whether to plot the absolute number (instead of the fraction) of data points less than or equal than some value.
            
        Returns
        -------
        abscissae : array
            The positions of steps in the ECDF.

        ordinates : array
            The lower values of the steps.
        
        lines : Lines object
            The lines object produced by the underlying plot call.
    &quot;&quot;&quot;
    if &quot;drawstyle&quot; in kwargs and kwargs[&quot;drawstyle&quot;]!=&quot;steps-pre&quot;:
        warn(&quot;Overwriting 'drawstyle' argument with 'steps-pre' to achieve a correct ECDF.&quot;)
    kwargs[&quot;drawstyle&quot;] = &quot;steps-pre&quot;
    
    points = ecdf_points(x,remove_redundant,absolute)
    lines = axes.plot(*points,*args,**kwargs)
    return (*points,lines)

fig,axes = plt.subplots()
ecdf(axes,test_data,remove_redundant=True,absolute=True)
axes.hist(test_data,bins=1000,cumulative=True);

fig,axes = plt.subplots()
ecdf(axes,test_data,remove_redundant=True)
axes.hist(test_data,bins=1000,cumulative=True,density=True);"><pre class="notranslate"><span class="pl-k">from</span> <span class="pl-s1">warnings</span> <span class="pl-k">import</span> <span class="pl-s1">warn</span>
<span class="pl-k">import</span> <span class="pl-s1">numpy</span> <span class="pl-k">as</span> <span class="pl-s1">np</span>
<span class="pl-k">from</span> <span class="pl-s1">matplotlib</span> <span class="pl-k">import</span> <span class="pl-s1">pyplot</span> <span class="pl-k">as</span> <span class="pl-s1">plt</span>

<span class="pl-k">def</span> <span class="pl-en">ecdf_points</span>(<span class="pl-s1">data</span>,<span class="pl-s1">remove_redundant</span>,<span class="pl-s1">absolute</span><span class="pl-c1">=</span><span class="pl-c1">False</span>):
    <span class="pl-k">assert</span> <span class="pl-s1">data</span>.<span class="pl-s1">ndim</span><span class="pl-c1">==</span><span class="pl-c1">1</span>
    <span class="pl-k">assert</span> <span class="pl-s1">np</span>.<span class="pl-en">all</span>(<span class="pl-s1">np</span>.<span class="pl-en">isfinite</span>(<span class="pl-s1">data</span>))
    
    <span class="pl-s1">abscissae</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">append</span>(<span class="pl-s1">data</span>,<span class="pl-s1">np</span>.<span class="pl-s1">inf</span>)
    <span class="pl-s1">abscissae</span>.<span class="pl-en">sort</span>()
    
    <span class="pl-k">if</span> <span class="pl-s1">absolute</span>:
        <span class="pl-s1">ordinates</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">arange</span>(<span class="pl-en">len</span>(<span class="pl-s1">abscissae</span>),<span class="pl-s1">dtype</span><span class="pl-c1">=</span><span class="pl-s1">int</span>)
    <span class="pl-k">else</span>:
        <span class="pl-s1">ordinates</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">linspace</span>(<span class="pl-c1">0</span>,<span class="pl-c1">1</span>,<span class="pl-en">len</span>(<span class="pl-s1">abscissae</span>)) 
    
    <span class="pl-k">if</span> <span class="pl-s1">remove_redundant</span>:
        <span class="pl-s1">needed</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">ediff1d</span>(<span class="pl-s1">abscissae</span>,<span class="pl-s1">to_begin</span><span class="pl-c1">=</span><span class="pl-c1">True</span>).<span class="pl-en">astype</span>(<span class="pl-s1">bool</span>)
        <span class="pl-s1">abscissae</span> <span class="pl-c1">=</span> <span class="pl-s1">abscissae</span>[<span class="pl-s1">needed</span>]
        <span class="pl-s1">ordinates</span> <span class="pl-c1">=</span> <span class="pl-s1">ordinates</span>[<span class="pl-s1">needed</span>]
    
    <span class="pl-k">return</span> <span class="pl-s1">abscissae</span>, <span class="pl-s1">ordinates</span>

<span class="pl-s1">test_data</span> <span class="pl-c1">=</span> <span class="pl-s1">np</span>.<span class="pl-en">array</span>([<span class="pl-c1">4</span>,<span class="pl-c1">2</span>,<span class="pl-c1">3</span>,<span class="pl-c1">2</span>,<span class="pl-c1">4</span>,<span class="pl-c1">3</span>,<span class="pl-c1">1</span>,<span class="pl-c1">2</span>,<span class="pl-c1">1</span>,<span class="pl-c1">4</span>])
<span class="pl-s1">np</span>.<span class="pl-s1">testing</span>.<span class="pl-en">assert_allclose</span>(
    <span class="pl-en">ecdf_points</span>(<span class="pl-s1">test_data</span>,<span class="pl-s1">remove_redundant</span><span class="pl-c1">=</span><span class="pl-c1">True</span>),
    [ [<span class="pl-c1">1</span>,<span class="pl-c1">2</span>,<span class="pl-c1">3</span>,<span class="pl-c1">4</span>,<span class="pl-s1">np</span>.<span class="pl-s1">inf</span>], [<span class="pl-c1">0</span>,<span class="pl-c1">0.2</span>,<span class="pl-c1">0.5</span>,<span class="pl-c1">0.7</span>,<span class="pl-c1">1.0</span>] ],
)
<span class="pl-s1">np</span>.<span class="pl-s1">testing</span>.<span class="pl-en">assert_equal</span>(
    <span class="pl-en">ecdf_points</span>(<span class="pl-s1">test_data</span>,<span class="pl-s1">remove_redundant</span><span class="pl-c1">=</span><span class="pl-c1">True</span>,<span class="pl-s1">absolute</span><span class="pl-c1">=</span><span class="pl-c1">True</span>),
    [ [<span class="pl-c1">1</span>,<span class="pl-c1">2</span>,<span class="pl-c1">3</span>,<span class="pl-c1">4</span>,<span class="pl-s1">np</span>.<span class="pl-s1">inf</span>], [<span class="pl-c1">0</span>,<span class="pl-c1">2</span>,<span class="pl-c1">5</span>,<span class="pl-c1">7</span>,<span class="pl-c1">10</span>] ],
)


<span class="pl-k">def</span> <span class="pl-en">ecdf</span>(<span class="pl-s1">axes</span>,<span class="pl-s1">x</span>,<span class="pl-c1">*</span><span class="pl-s1">args</span>,<span class="pl-s1">remove_redundant</span><span class="pl-c1">=</span><span class="pl-c1">False</span>,<span class="pl-s1">absolute</span><span class="pl-c1">=</span><span class="pl-c1">False</span>,<span class="pl-c1">**</span><span class="pl-s1">kwargs</span>):
    <span class="pl-s">"""</span>
<span class="pl-s">        Plot the empirical cumulative distribution function, i.e., the fraction of data points less than or equal than some value. Returns the supporting points of the underlying step plot.</span>
<span class="pl-s"></span>
<span class="pl-s">        Parameters</span>
<span class="pl-s">        ----------</span>
<span class="pl-s">        x : one-dimensional array</span>
<span class="pl-s">            Input values</span>
<span class="pl-s">        </span>
<span class="pl-s">        remove_redundant : bool, optional</span>
<span class="pl-s">            Whether spurious points arising from identical data points shall be removed. This is mainly useful for large datasets containing many identical data points, where you get an unnecessarily large or inefficient figure otherwise. If your data does not contain any duplicate points, this has no effect and just uses some time and memory.</span>
<span class="pl-s">        </span>
<span class="pl-s">        absolute : bool, optional</span>
<span class="pl-s">            Whether to plot the absolute number (instead of the fraction) of data points less than or equal than some value.</span>
<span class="pl-s">            </span>
<span class="pl-s">        Returns</span>
<span class="pl-s">        -------</span>
<span class="pl-s">        abscissae : array</span>
<span class="pl-s">            The positions of steps in the ECDF.</span>
<span class="pl-s"></span>
<span class="pl-s">        ordinates : array</span>
<span class="pl-s">            The lower values of the steps.</span>
<span class="pl-s">        </span>
<span class="pl-s">        lines : Lines object</span>
<span class="pl-s">            The lines object produced by the underlying plot call.</span>
<span class="pl-s">    """</span>
    <span class="pl-k">if</span> <span class="pl-s">"drawstyle"</span> <span class="pl-c1">in</span> <span class="pl-s1">kwargs</span> <span class="pl-c1">and</span> <span class="pl-s1">kwargs</span>[<span class="pl-s">"drawstyle"</span>]<span class="pl-c1">!=</span><span class="pl-s">"steps-pre"</span>:
        <span class="pl-en">warn</span>(<span class="pl-s">"Overwriting 'drawstyle' argument with 'steps-pre' to achieve a correct ECDF."</span>)
    <span class="pl-s1">kwargs</span>[<span class="pl-s">"drawstyle"</span>] <span class="pl-c1">=</span> <span class="pl-s">"steps-pre"</span>
    
    <span class="pl-s1">points</span> <span class="pl-c1">=</span> <span class="pl-en">ecdf_points</span>(<span class="pl-s1">x</span>,<span class="pl-s1">remove_redundant</span>,<span class="pl-s1">absolute</span>)
    <span class="pl-s1">lines</span> <span class="pl-c1">=</span> <span class="pl-s1">axes</span>.<span class="pl-en">plot</span>(<span class="pl-c1">*</span><span class="pl-s1">points</span>,<span class="pl-c1">*</span><span class="pl-s1">args</span>,<span class="pl-c1">**</span><span class="pl-s1">kwargs</span>)
    <span class="pl-k">return</span> (<span class="pl-c1">*</span><span class="pl-s1">points</span>,<span class="pl-s1">lines</span>)

<span class="pl-s1">fig</span>,<span class="pl-s1">axes</span> <span class="pl-c1">=</span> <span class="pl-s1">plt</span>.<span class="pl-en">subplots</span>()
<span class="pl-en">ecdf</span>(<span class="pl-s1">axes</span>,<span class="pl-s1">test_data</span>,<span class="pl-s1">remove_redundant</span><span class="pl-c1">=</span><span class="pl-c1">True</span>,<span class="pl-s1">absolute</span><span class="pl-c1">=</span><span class="pl-c1">True</span>)
<span class="pl-s1">axes</span>.<span class="pl-en">hist</span>(<span class="pl-s1">test_data</span>,<span class="pl-s1">bins</span><span class="pl-c1">=</span><span class="pl-c1">1000</span>,<span class="pl-s1">cumulative</span><span class="pl-c1">=</span><span class="pl-c1">True</span>);

<span class="pl-s1">fig</span>,<span class="pl-s1">axes</span> <span class="pl-c1">=</span> <span class="pl-s1">plt</span>.<span class="pl-en">subplots</span>()
<span class="pl-en">ecdf</span>(<span class="pl-s1">axes</span>,<span class="pl-s1">test_data</span>,<span class="pl-s1">remove_redundant</span><span class="pl-c1">=</span><span class="pl-c1">True</span>)
<span class="pl-s1">axes</span>.<span class="pl-en">hist</span>(<span class="pl-s1">test_data</span>,<span class="pl-s1">bins</span><span class="pl-c1">=</span><span class="pl-c1">1000</span>,<span class="pl-s1">cumulative</span><span class="pl-c1">=</span><span class="pl-c1">True</span>,<span class="pl-s1">density</span><span class="pl-c1">=</span><span class="pl-c1">True</span>);</pre></div>
<p dir="auto">Sometimes, you want to display some text and only one piece of text, but you don't want to precompute where it should be. So it would be nice if matplotlib could figure this out for you, just as it does with <code class="notranslate">legend(loc='best')</code>. This question came up on SO a while ago:</p>
<p dir="auto"><a href="http://stackoverflow.com/questions/7045729/automatically-position-text-box-in-matplotlib" rel="nofollow">http://stackoverflow.com/questions/7045729/automatically-position-text-box-in-matplotlib</a></p>
<p dir="auto">There's also an old ticket (<a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="1087914" data-permission-text="Title is private" data-url="https://github.com/matplotlib/matplotlib/issues/176" data-hovercard-type="issue" data-hovercard-url="/matplotlib/matplotlib/issues/176/hovercard" href="https://github.com/matplotlib/matplotlib/issues/176">#176</a>) that was looking to hack <code class="notranslate">legend(numpoints=0, loc='best')</code> in order to achieve the same effect.</p>