Displaying Lithology Data on a Well Log Plot Using Python

Displaying Lithology Data on a Well Log Plot Using Python

Using fill_betweenx() in matplotlib to add variable color fills and hatches for geological lithology data

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

Adding lithology information to a well log plot can enhance a petrophysical or geological interpretation. It can be used to understand why some log responses may behave the way they do. This data may have been sourced from a previous mineralogical interpretation or from mud logs.

In my previous article: Enhancing Visualization of Well Logs With Plot Fills, we saw: how to apply fixed color fill between a curve and the edge of a track, how to apply a density-neutron crossover fill and how to apply a variable fill based upon the value of curve being plotted.

In this article will cover how to use a variable fill to apply not only a color to our plot, but also a hatching. Using these two options allows us to generate lithological fill.

This article forms part of my Python & Petrophysics series. The full series can be found here. For the examples below you can find my Jupyter Notebook and dataset on my GitHub repository at the following link.

To follow along, the Jupyter Notebook can be found at the link above and the data file for this article can be found in the Data subfolder of the Python & Petrophysics repository.

Setting Up & Displaying Lithology Fills on a Well Log Plot

Setting up the Libraries

The first step is to bring in the libraries we will be working with for this article. We will be using just two libraries: pandas and matplotlib. This will allow us to load our data to a dataframe and display it on a plot.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

Loading the Data

The dataset we will be using for this article comes from a recent Machine Learning competition for lithology prediction that was run by Xeek and FORCE (https://xeek.ai/challenges/force-well-logs/overview). The objective of the competition was to predict lithology from a dataset consisting 98 training wells each with varying degrees of log completeness. The objective was to predict lithofacies based on the log measurements. To download the file, navigate to the Data section of the link above.

The data can be loaded using pd.read_csv(). As this is quite a large dataset, we will use a single well from it to work with and we will also take just the curves (columns) we need. We can do that by taking a subset of the data like so:

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

To make things simpler we will rename the FORCE_2020_LITHOFACIES_LITHOLOGY column to just LITHOLOGY . This is done by using the rename function on the dataframe and passing a dictionary of the old name and the new name. Note that using the inplace=True argument will replace the column name in the original dataframe.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

When we call upon our dataframe using data.head() we can see that our dataset is small and our lithology column has been renamed.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

Setting up the Lithologies Using a Nested Dictionary

The lithologies currently listed in the lithology column contain a series of numbers. We can map these in a nested dictionary where we have the full name of the lithology, a simplified number (if required for conversion), the hatch style, and the color of the fill.

The colors are based on the Kansas Geological Survey website. However, hatching symbols are limited in the default setup of matplotlib. In a future article, I will cover how to create custom hatches and lithologies.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

We can quickly convert our nested dictionary to a pandas dataframe to make it easier for humans to read by using the from_dict function like so.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

This will return a nicely formatted table where we can see the colors and hatches for each lithology code.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

By just looking at the dictionary, it can be difficult to understand how each of these will look. To solve this, we can quickly create a simple image showing the different hatchings and colors for each lithology.

To do this, we will first define some co-ordinates for two new variables x and y. Then we will setup the matplotlib figure and axes using subplots:

fig, axes = plt.subplots(ncols=4, nrows=3, sharex=True, sharey=True, fig size=(10,5), subplot_kw={'xticks':[], 'yticks':[]})

This allows us to define the size of the plot (4 columns by 3 rows), the figure size, and turning off the tick marks on both axes.

We then want to loop through the flattened axes and the nested dictionary to add the data and hatched color fills to each subplot. As we are dealing with a nested dictionary, we need to us square brackets multiple times [ ][ ] to go in two levels. The first level is the key, and the second level is the key of the nested dictionary. For example, to retrieve the color we can access it by typing in lithology_numbers[30000]['color']

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

This generates the following legend that we can refer to later on. Notice that the lithology symbols do not 100% match the Kansas Geological Survey chart. We will see how to resolve this in a future article.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

Setting up the Well Log Plot With a Lithology Track

In previous articles (Enhancing Visualization of Well Logs With Plot Fills and Exploratory Data Analysis With Well Log Data) I have set the plot up on the fly and passed in the dataframe I am working with. In this article, I will be creating a simple makeplot function which we can pass the dataframe into.

This allows us to easily reuse the code for other dataframes, assuming the curve names are the same. This could be further refined to make it more generic. This new function takes in three arguments, the dataframe, the top_depth, and the bottom depth.

This code will generate a well log plot with 3 tracks one for the Gamma Ray, one containing both Neutron Porosity and Bulk Density, and the final one containing our geological lithology data.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

The key piece of code to ensure our lithology fill_betweenx function from matplotlib creates a variable fill is shown below. All we are doing is looping over each of the keys within the dictionary, assigning simpler variable names to the features within the dictionary, and then using the fill_betweenx function to check if the value in our lithology column equals the dictionary key. If it does, then it will apply the relevant color and hatching to the fill. It will then move on to the next key and repeat until all keys have been cycled through.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

Displaying the Well Log Plot & Lithology

Once we have the plot set up as a function. We can simply pass in our dataframe and the depths that we want to plot. In this example, we will focus on a small section of the data so that we are able to see the change in the plot fills.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

Once we do this, the plot below will appear.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

To see the lithology fill in more detail, we can zoom in by changing the depth range values.

Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data.
Well log plot with gamma ray, neutron porosity and bulk density data plotted alongside lithology data. Image created by the author.

Summary

In this article we have covered how to setup and display lithology data using matplotlib’s fill_betweenx() function. We saw how a nested dictionary can be used to store parameters for each lithology and then be called upon using a for loop to fill with the correct color and hatch.

Adding this data to a plot can enhance how the geoscientist or petrophysicist relates the electrical logging measurements to the geology.

Thanks for reading!

If you have found this article useful, please feel free to check out my other articles looking at various aspects of Python and well log data. You can also find my code used in this article and others at GitHub.

If you want to get in touch you can find me on LinkedIn or at my website.

Interested in learning more about python and well log data or petrophysics? Follow me on Medium.

References

Kansas Geological Survey, Lithology Symbols. http://www.kgs.ku.edu/PRS/Ozark/PROFILE/HELP/DATA_ENTRY/lithology/Lithology-Symbols.html

“Lithofacies data was provided by the FORCE Machine Learning competition with well logs and seismic 2020” Bormann P., Aursand P., Dilib F., Dischington P., Manral S. 2020. 2020 FORCE Machine Learning Contest. https://github.com/bolgebrygg/Force-2020-Machine-Learning-competition

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *