<jupyter_start><jupyter_text>Tutorial: Getting Started with PythonDATE: Monday June 8, 2020 08:00 - 11:00 UTCAUDIENCE: BeginnerINSTRUCTOR: <NAME>, Digital Geoscientist, [Agile](https://agilescientific.com/) Video Stream:     WelcomeWelcome to Transform 2020 and to Getting Started with Python! This session is open to all but is aimed at people who have never coded at all or who don't know Python. Ideally you should have watched the setup videos for [Windows](https://youtu.be/FdatS_NKVrM) or [Linux](https://youtu.be/3ncwbHyZeAg) and/or followed the instructions in the [Installation guide for the tutorials](http://swu.ng/t20-python-setup) that contained the information in those videos and more. This session is meant as a demo but if you followed those installation steps, you should be able to follow along. All session details are available [here](https://transform2020.sched.com/). AgendaSTART:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;08.00 UTCBREAK:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;09.30 UTCRESUME:&nbsp;&nbsp;&nbsp;09.40 UTCEND:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;11.00 UTC Content- Simple math, logic and flow control- Modelling [Darcy's law](https://en.wikipedia.org/wiki/Darcy%27s_law) with a function- Computing fold wavelengths with the [Ramberg-Biot](https://blogs.agu.org/mountainbeltway/2010/10/15/friday-fold-wavelength-contrast/) equation- Geophysical wavelets with [`bruges`](https://github.com/agile-geoscience/bruges)- Mapping DEM's with [`matplotlib`](https://matplotlib.org/)- Data wrangling and QC well data with [`welly`](https://github.com/agile-geoscience/welly)- Scanning a seismic cube with [`segyio`](https://github.com/equinor/segyio) Set upThe [anaconda documentation](https://docs.conda.io/projects/conda/en/latest/user-guide/concepts/environments.htmlconda-environments) tells us that: "A conda environment [...] contains a specific collection of conda packages that you have installed." We use these environments to isolate different projects.We will be using an environment called `transform2020` (but you can call it whatever you wish) that was created with:`conda create --name transform2020 python=3.7 anaconda`We then also install the [welly](https://github.com/agile-geoscience/welly), [segyio](https://github.com/equinor/segyio) and [bruges](https://github.com/agile-geoscience/bruges) packages to import \*.las and \*.sgy files and to perform geophysical calculations. First we activated the environment:`conda activate transform2020`and then installed the additional libraries:`pip install welly segyio bruges`You can then access this environment inside a jupyter notebook using one of two methods:1. By running `pip install nb_conda_kernels` in `base` and then running `jupyter notebook` with `base` activated.2. By running `python -m ipykernel install --user --name transform2020` after creating the `transform2020` environment and then running `jupyter notebook` with either `base` or `transform2020` activated.In both cases you then need to select the `transform2020` environment by selecting `Kernel>Change kernel>kernel name` in the jupyter notebook browser window.  Python basics: numbers and functions Let's express an equation symbolically, then in Python.$$ t = xy + z $$ EXERCISEWrite the equation as a line of Python code, using the same symbols.You'll also need to define some variables. Use 3, 10, and 1 for `x`, `y`, and `z` respectively. Here's how that looks in Python: Fine, but it's not very convenient to have to define `x`, `y`, and `z`, then write out the equation. If I want to do it again on different values of `x`, `y`, and `z`, then I'll have to write it all out again.<jupyter_code>x = 4
y = 100
z = 0.1

t = x * y + z

t<jupyter_output><empty_output><jupyter_text>Instead, we can define a function:<jupyter_code>compute_t(3, 10, 1)
compute_t(4, 100, 0.1)<jupyter_output><empty_output><jupyter_text>There's no reason we can't pass variables into here:<jupyter_code>compute_t(a, b, 5)<jupyter_output><empty_output><jupyter_text>What if we want to compute a range of values for `x`?<jupyter_code>compute_t(x, 10, 1)<jupyter_output><empty_output><jupyter_text>Pretty convenient!And what if we need to check for conditions?We can use `if ... else` statements for this:  Transforming geoscience equations into Python codeWe will take some classical geoscience equations and see how to code them in Python, both for single-use and in `functions` for reuse.  Darcy's law[Darcy's law](https://en.wikipedia.org/wiki/Darcy%27s_law) describes flow rate in porous media:$$ Q = \frac{kA}{\mu L} \Delta P$$where:- $Q$ = flow rate [m3/s]- $k$ = permeability [m2]- $A$ = cross-sectional area [m2]- $\mu$ = viscosity [Pa.s]- L = length of sample [m]- $\Delta P$ = pressure difference accross the sample [Pa]All units are SI. EXERCISEImplement Darcy's law in Python. You can do it either as a 'raw' Python expression, like the `t = x * y + z` example above, or as a function, like the `compute_t()` example. Alternatively, using a Python function: Let's use some more realistic values.Let's consider a side wall core:End view | Side view:-- | :-- ![alt](./data/swc_end.png) | ![alt](./data/swc_side.png)<jupyter_code>k = 4.9346165e-13 # ~500 mD
A = 2.03e-3       # 1 inch diameter plug
mu = 8.9e-4       # ~dynamic viscosity of water at about 25°C
L = 0.0508        # 2 inch plug
dP = 689476       # ~100 psi

Q = flow_rate(k, A, mu, L, dP)

print(f'The flow rate `Q` is approx: {Q:.1e} m3/s or {Q * 3600:.2f} m3/hr')<jupyter_output><empty_output><jupyter_text>Let's use a NumPy array to make a plot of flow rate vs plug length. We'll need to define the range of plug lengths:<jupyter_code>Q = flow_rate(k, A, mu, L, dP) * 3600
%matplotlib inline
import matplotlib.pyplot as plt  

plt.plot(L, Q)
plt.show()
from ipywidgets import interact

@interact(dP=(0, 4, 0.1))
def make_plot(dP):
    
    # We have to compute Q every time the widget changes...
    Q = flow_rate(k, A, mu, L, dP*1e6) * 3600
    
    # ...and then make the plot again.
    fig, ax = plt.subplots(figsize=(16,6))
    ax.plot(L, Q)
    ax.grid()
    ax.set_title('Flow rate [m3/hr] versus plug length [m]', fontsize=16)
    ax.set_ylabel('Flow rate [m3/hr]', fontsize=14)
    ax.set_xlabel('Plug length [m]', fontsize=14)
    ax.text(0.83, 0.088, f"dP: {dP} MPa", size=20)
    ax.set_ylim(0, 0.1)
    plt.show()
    
    return<jupyter_output><empty_output><jupyter_text>Ramberg-Biot EquationThe [Ramberg-Biot equation](http://www.files.ethz.ch/structuralgeology/JPB/files/English/7folding.pdf) describes the wavelength $L$ of a fold:$$ L = 2 \pi T \left(\frac{\nu}{6\nu_0}\right)^{1/3}$$where:- $L$ = wavelength of the fold [m]- $\pi$ = 3.14 [unitless]- $T$ = folded layer thickness [m]- $\nu$ = viscosity of folded layer [Pa.s]- $\nu_0$ = viscosity of cross-cut layer [Pa.s]An [example](https://blogs.agu.org/mountainbeltway/2010/10/15/friday-fold-wavelength-contrast/) of the Ramberg-Biot equation in action.This time we will define the function directly, notice we are using the $\pi$ constant from `numpy`.First an example to discuss what we are looking into:<jupyter_code>fig, ax = plt.subplots(figsize=(16,4))
ax.plot(-1*np.sin(np.sin(np.linspace(0,3*np.pi,100))),lw=4, c='g')
ax.plot(-1*np.sin(np.linspace(0,5*np.pi,100)) - 2,lw=4, c='b')
ax.set_yticklabels([])
ax.set_xticks(np.arange(0,110,10))
ax.grid()

ax.set_title('Simplified illustration of Ramberg-Biot usage', fontsize=16)
textstr_0 = 'Long wavelength in high\nviscosity enclosing medium'
textstr_1 = 'Short wavelength in low\nviscosity enclosing medium'
props_cret = dict(boxstyle='square', facecolor='lightgreen', alpha=0.4)
props_jur = dict(boxstyle='square', facecolor='lightblue', alpha=0.4)
props_well = dict(boxstyle='square', facecolor='lightgrey', alpha=0.4)

ax.axvline(50, lw=3, c='k', ls=':')

ax.text(0.09, 0.95, textstr_0, transform=ax.transAxes, fontsize=14,
        verticalalignment='top', bbox=props_cret)
ax.text(0.01, 0.48, textstr_1, transform=ax.transAxes, fontsize=14,
        verticalalignment='top', bbox=props_jur)
ax.text(0.7, 0.78, 'Cretaceous', transform=ax.transAxes, fontsize=14,
        verticalalignment='top', bbox=props_cret)
ax.text(0.77, 0.4, 'Jurassic', transform=ax.transAxes, fontsize=14,
        verticalalignment='top', bbox=props_jur)
ax.text(0.48, 0.55, 'Well', transform=ax.transAxes, fontsize=14,
        verticalalignment='top', bbox=props_well, rotation='vertical')

plt.show()<jupyter_output><empty_output><jupyter_text>Gara Anticline - Kurdistan (satellite) | Gara Anticline - Kurdistan (terrain):-- | :-- ![alt](./data/gara_satellite.png) | ![alt](./data/gara_terrain.png)[Google map source location](https://www.google.ch/maps/@36.9742638,43.6135018,12z/data=!5m1!1e4) EXERCISEWrite the Ramberg-Biot equation in a Python function, using the same variable names as shown above. Given an input data set with the required arguments:|Period    |Formation                   |Thickness [m]|$\nu$ [Pa.s]|$\nu_0$ [Pa.s]||:---------|:---------------------------|:-------:|:---:|:-----:||Cretaceous|Aqra Bekhme Kometan Qamchuqa|730      |19   |17     ||Jurassic  |Naoke<NAME>lu          |80       |17   |19     ||Jurassic  |Sekhanian Sargelu           |420      |19   |17     ||Triassic  |Baluti                      |30       |17   |19     ||Triassic  |Kurra Chine                 |834      |18   |17     ||Triassic  |Geli                        |575      |18   |17     ||Triassic  |Beduh                       |64       |17   |19     ||Triassic  |<NAME>                   |811      |19   |17     |***Nota Bene***: The thickness values were measured from wells, the viscosity values are simple examples values but are not measured.We could print out the results of the calculation for each row, but we'll first need to store this table in a python data structure.We'll put the values together in a [`tuple`](https://docs.python.org/3.7/library/stdtypes.html?highlight=tupletuple), this will allow us to `index` into them to retrieve one value at a time. But then we'll put all of these values together in a dictionnary (a python [`dict`](https://docs.python.org/3.7/tutorial/datastructures.htmldictionaries)) where the keys will be the formation names:<jupyter_code>data = {'ABKomQam':(730,19,17),
        'NaokSargelu': (80,17,19),
        'SekhSargelu': (420,19,17),
        'Baluti': (30,17,19),
        'KurraChine': (834,18,17),
        'Geli': (575,18,17),
        'Beduh': (64,17,19),
        'ChiaZara': (811,19,17),
       }<jupyter_output><empty_output><jupyter_text>Or save the results directly to a CSV file using the Python inbuilt [CSV module](https://docs.python.org/3.7/library/csv.htmlmodule-csv):<jupyter_code>import csv

wavelengths = {}
for formation, inputs in data.items():
    T, nu, nu_zero = inputs
    rb = ramberg_biot(T, nu, nu_zero)
    wavelengths[formation] = rb

with open('./data/wavelengths.csv', 'w') as f:
    w = csv.writer(f)
    w.writerows(wavelengths.items())<jupyter_output><empty_output><jupyter_text>We could also have used [pandas](https://pandas.pydata.org/) to write _to_ disk but let's now read _from_ disk using it's `.read_csv` method:<jupyter_code>import pandas as pd

df_wave = pd.read_csv('./data/wavelengths.csv', names=['Formation', 'Wavelength'], index_col='Formation')
df_wave<jupyter_output><empty_output><jupyter_text>We could also plot the results:We can make a simple - but not very legible - plot with one line of code:<jupyter_code>plt.bar(df_wave.index, df_wave['Wavelength'])<jupyter_output><empty_output><jupyter_text>But now let's dress that plot up to make it more useful, first we'll set up a [`list`](https://docs.python.org/3.7/library/stdtypes.html?highlight=listlists) to store [hex color codes](https://www.color-hex.com/), and then change the figure size, add a title, labels and a grid.<jupyter_code>colors = ['#339966', # Cretaceous
          '#333399', # Jurassic
          '#333399', # Jurassic
          '#CC99FF', # Upper Triassic
          '#CC99FF', # Upper Triassic
          '#800080', # Middle Triassic
          '#800080', # Lower Triassic
          '#800080', # Lower Triassic
         ]
fig = plt.figure(figsize=(15,4))
plt.bar(df_wave.index,
        df_wave['Wavelength'],
        color=colors,
        zorder=2,
        width=0.5,
       )
plt.title('Example fold wavelengths for northern Kurdish stratigraphy\nfrom Ramberg-Biot equation estimates',
          fontsize=16
         )
plt.ylabel('Wavelength [m]', fontsize=14)
plt.xticks(range(8), (wavelengths.keys()), rotation=90, fontsize=12)
plt.grid(axis='y', zorder=1)
plt.show()<jupyter_output><empty_output><jupyter_text>Geophysical equationsIf you're into geophysics, for many equations you won't even have to code them yourself as there is a ready-made repo called [bruges](https://github.com/agile-geoscience/bruges) which stands for:    There are multiple modules inside Bruges:<jupyter_code>import bruges

dir(bruges)<jupyter_output><empty_output><jupyter_text>For example if you want to make a ricker wavelet, bruges has you covered: EXERCISEUse the bruges library to plot a ricker wavelet using matplotlib's `plt.plot()` function.<jupyter_code>import ipywidgets as widgets

@interact(duration=widgets.FloatSlider(value=0.256,min=0.04,max=0.512,step=0.004,
                                      description='duration',
                                      continuous_update=False,
                                      readout_format='.3f'),
          dt=widgets.FloatSlider(value=0.001,min=0.0001,max=0.008,step=0.0001,
                                      description='dt',
                                      continuous_update=False,
                                      readout_format='.4f'),
          frequency=widgets.IntSlider(value=25,min=1,max=75,step=1,
                                      description='frequency',
                                      continuous_update=False,
                                      readout_format='d'),       
          filled=widgets.Checkbox(value=True,description='fill wavelet',disabled=False)
         )
def plot_ricker(duration, dt, frequency, filled):
    """
    Plot a filter:
    Args:
        function (function): one of ['ricker', 'gabor', 'sinc', 'cosine']
        duration (float): The length in seconds of the wavelet.
        dt (float): The sample interval in seconds.
        frequency (ndarray): Dominant frequency of the wavelet in Hz.
        fill (boolean): whether the filter plot is filled between 0 and wavelet.
    Returns:
        ndarray. {function} wavelet with centre frequency 'frequency' sampled on t.
    """
    # call the wavelet function
    w, t = bruges.filters.ricker(duration, dt, f=frequency, return_t=True)

    # create the plot
    fig, ax = plt.subplots(figsize=(15, 6), ncols=1) 
    ax.plot(t, w, color='black')
    ax.grid()
    ax.set_title(f'Ricker, frequency={frequency}, duration={duration}, dt={dt}')    
    
    # define fill_between() parameters
    x_min = -duration / 2
    x_max = duration / 2
    x = np.arange(x_min, x_max, dt)
    
    # fill wavelet
    if filled:
        ax.fill_between(x, 0, w, where=w > 0, color='k')

    # show the plot
    plt.show()
    
    return<jupyter_output><empty_output><jupyter_text>Plotting a DEM and making cross sectionsIn this section, we'll make a digital elevation map of Mount St-Helens after the [1980 eruption](https://en.wikipedia.org/wiki/1980_eruption_of_Mount_St._Helens) to see the crater left by the eruption, and we'll also plot some cross-sections through the mountain comparing the topography pre- and post-eruption.First we'll use [`numpy`](https://numpy.org/) to load some data we prepared earlier:<jupyter_code>before = np.load('./data/st-helens_before.npy')
after = np.load('./data/st-helens_after.npy')<jupyter_output><empty_output><jupyter_text>Then set up some variables:<jupyter_code>difference = before - after
fontsize = 14
linewidth = 3
ew_section1 = 190
ns_section1 = 150<jupyter_output><empty_output><jupyter_text>And finally make the [`figure` and `axes`](https://matplotlib.org/3.1.1/gallery/showcase/anatomy.html), first setting up the `figure` and then adding `axes` and specifying their positions to set up our plot just as we want it, we'll also add some `titles` and `labels`:<jupyter_code>fig = plt.figure(figsize = (12,8))
ax = fig.add_axes([0.1,0.1,0.8,0.8])
ax.set_title('Mount St-Helens Digital Elevation Model map', fontsize=fontsize)
ax.imshow(after)
ax.axhline(ew_section1, color='red', lw=linewidth)
ax.axvline(ns_section1, color='blue', lw=linewidth)
ax.set_ylabel('Northing [index]', size=fontsize)
ax.set_xlabel('Easting [index]', size=fontsize)
ax.grid(alpha=0.5)

ax2 = fig.add_axes([0.8,0.58,0.5,0.32])
ax2.set_title('Mount St-Helens West-East cross-section', fontsize=fontsize)
ax2.plot(before[ew_section1], c='red')
ax2.plot(after[ew_section1], c='red')
ax2.set_xlabel('Easting [index]', size=fontsize)
ax2.set_ylabel('Elevation [m]')
ax2.grid()

ax3 = fig.add_axes([0.8,0.1,0.5,0.32])
ax3.set_title('Mount St-Helens North-South cross-section', fontsize=fontsize)
ax3.plot(before[:,ns_section1], c='blue')
ax3.plot(after[:,ns_section1], c='blue')
ax3.set_xlabel('Northing [index]', size=fontsize)
ax3.set_ylabel('Elevation [m]')
ax3.grid()<jupyter_output><empty_output><jupyter_text>Loading up well files and plotting curvesFirst let's see what files we have with the inbuilt [`glob`](https://docs.python.org/3.7/library/glob.htmlmodule-glob) module:  Inspecting a single well  Inspecting a single curve  Smoothing a curve  Plot all curves in one well  ...or in a group of wells  A Well has a `.df()` method that returns a pandas DataFrameWe'll use the `.colums` attribute on the `DataFrame` to choose the logs we want: And then get some statistics for two curves using the [`.describe`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.describe.html) method on the DataFrame:  Log QCThe [`welly`](https://github.com/agile-geoscience/welly) library gives us access to some QC capabilities to rapidly inspect our logs - this might be particularly useful to prepare a Machine Learning task for example:<jupyter_code>import welly.quality as qc
from IPython.display import HTML

tests = {
    'each': [qc.no_gaps, qc.no_monotonic],
    'GR': [qc.all_positive],
    'RHOB': [qc.all_positive],
}

results = well_R39.qc_table_html(tests=tests)

HTML(results)<jupyter_output><empty_output><jupyter_text>Plot multiple logs of the same wellNow we'll set up a log panel by using [`striplog`'s](https://github.com/agile-geoscience/striplog) [`Legend` module](https://striplog.readthedocs.io/en/latest/api/striplog.legend.htmlmodule-striplog.legend). We select some logs we want to plot in different tracks, then define a legend for the display of each curve:<jupyter_code>from striplog import Legend

tracks = ['MD', 'CALI', 'GR', 'RHOB', 'NPOR', ['DT1R', 'DT2', 'DT2R']]

legend = Legend.builtin('NSDOE')

curve_legend_csv = """colour,lw,ls,xlim,xscale,curve mnemonic
black,0.5,-,,linear,CALI
green,1.0,-,"0,200",linear,GR
red,1.0,-,,linear,RHOB
blue,1.0,-,,linear,NPOR
red,0.4,-,,linear,DT1R
#e6153b,0.4,-,,linear,DT2
#c7324d,0.4,-,,linear,DT2R
"""

curve_legend = Legend.from_csv(text=curve_legend_csv)

complete_legend = legend + curve_legend<jupyter_output><empty_output><jupyter_text>And once this set up is done, we can make a plot for any given well using the `.plot` method and passing the `tracks` and `legend` variables to it:  Loading a seismic volume and inspecting slicesFor this task, we will use Equinor's [segyio](https://github.com/equinor/segyio) library which describes itself as follows:> Segyio is a small LGPL licensed C library for easy interaction with SEG Y formatted seismic data, with language bindings for Python and Matlab. Segyio is an attempt to create an easy-to-use, embeddable, community-oriented library for seismic applications. Features are added as they are needed; suggestions and contributions of all kinds are very welcome.It allows us to load 2D and 3D seismic, but of course with all the [usual caveats about the segy format](https://agilescientific.com/blog/2014/3/26/what-is-seg-y.html).So let's first import and inspect a curated seismic cube:If you don't have the file yet, **[get the large dataset from Agile's S3 bucket](https://s3.amazonaws.com/agilegeo/Penobscot_0-1000ms.sgy.zip)**. It's 140MB.<jupyter_code>import segyio

with segyio.open('./data/Penobscot_0-1000ms.sgy') as s:
    c = segyio.cube(s)<jupyter_output><empty_output><jupyter_text>EXERCISEPlot a single section of the seismic, for example an inline section.    You'll have to make sure inlines and crosslines are transposed so that the seabed is horizontal.  Scanning through a seismic volume interactivelyAs we did before, we can use `interact` to explore this cube on the fly, as long as it fits in memory of course. We will define a `seismic loading` function, a `seismic plotting` function, and finally call this plotting function with `interact`:<jupyter_code>def load_seismic(volume):
    with segyio.open(volume) as s:
        vol = segyio.cube(s)
    return vol
def seismic_plotter(colormap, section, inline, xline, timeslice, volume):
    """Plot a given seismic ILine, XLine or Timeslice with a choice of colormaps"""
    # load a seismic volume
    vol = load_seismic(volume)
    
    # display options for different sections
    sections = {
        'inline': {'amp': vol[inline,:,:].T, 'line': inline, 'shrink_val': 0.6, 
                  'axhline_y': timeslice, 'axhline_c': 'b', 
                  'axvline_x': xline, 'axvline_c': 'g',
                  'axspine_c': 'r',
                  'aspect': 1
                  },
        'xline': {'amp': vol[:,xline,:].T, 'line': xline, 'shrink_val': 0.5, 
                  'axhline_y': timeslice, 'axhline_c': 'b', 
                  'axvline_x': inline, 'axvline_c': 'r',
                  'axspine_c': 'g',
                  'aspect': 1
                 },
        'timeslice': {'amp': vol[:,:,timeslice], 'line': timeslice, 'shrink_val': 0.95, 
                  'axhline_y': xline, 'axhline_c': 'g', 
                  'axvline_x': inline, 'axvline_c': 'r',
                  'axspine_c': 'b',
                  'aspect': 0.5
                 },
    }

    # scale amplitudes
    ma = np.percentile(vol, 98)
    
    # plot figure
    fig, ax = plt.subplots(figsize=(18, 6), ncols=1)

    # apply options
    sec = sections[section]    
    im = ax.imshow(sec['amp'], aspect=sec['aspect'], vmin=-ma, vmax=ma, cmap=colormap)
    ax.set_title(f'Penobscot_0-1000ms {section} {sec["line"]}', fontsize=18)
    plt.colorbar(im, ax=ax, shrink=sec['shrink_val']).set_label('Amplitudes')
     
    # add projected lines
    ax.axhline(y=sec['axhline_y'], linewidth=1.5, color=sec['axhline_c'])
    ax.axvline(x=sec['axvline_x'], linewidth=1.5, color=sec['axvline_c'])
    for axis in ['top','bottom','left','right']:
        ax.spines[axis].set_linewidth(1.5)     
        ax.spines[axis].set_color(sec['axspine_c'])
    
    return    
_ = interact(seismic_plotter,
             colormap=['Greys', 'Greys_r', 'seismic', 'seismic_r', 'viridis', 'plasma', 'inferno', 'magma'],
             section=widgets.RadioButtons(options=['inline', 'xline', 'timeslice'],
                                          value='inline',description='slicer',disabled=False),
             inline=widgets.IntSlider(value=300,min=0,max=600,step=1,continuous_update=False,
                                      description='<font color="red">inline</>'),
             xline=widgets.IntSlider(value=240,min=0,max=480,step=1,continuous_update=False,
                                     description='<font color="green">xline</>'),
             timeslice=widgets.IntSlider(value=125,min=0,max=250,step=1,continuous_update=False,
                                         description='<font color="blue">timeslice</>'),
             volume='./data/Penobscot_0-1000ms.sgy',
            )<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1335499458468716, 'fraction_numerical': 0.030284095642755977, 'mean_word_length': 3.7784633757961785, 'pattern_counts': {'":': 0, '<': 94, '<?xml version=': 0, '>': 98, 'https://': 35, 'lorem ipsum': 0, 'www.': 3, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 18, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '21', 'dir': 'jupyter-structured-clean-dedup', 'id': '299664', 'license': 'Apache-2.0', 'n_tokens_mistral': 8590, 'n_tokens_neox': 7871, 'n_words': 2213, 'path': 'Getting_started_with_Python.ipynb', 'repo_name': 'Zabamund/transform2020-getting-started-python', 'size': '24023'}