Using Line Plots from Matplotlib to Create Simple Log Plots of Well Log Data

Using Line Plots from Matplotlib to Create Simple Log Plots of Well Log Data

Visualising well log data versus depth using the matplotlib library from Python

png
Well log plot created using the matplotlib Python library. Image by author.

Introduction

Well log plots are a common visualization tool within geoscience and petrophysics. They allow easy visualization of data (for example, Gamma Ray, Neutron Porosity, Bulk Density, etc) that have been acquired along the length (depth) of a wellbore. On these plots, we display our logging measurements on the x-axis and measured depth or true vertical depth on the y-axis.

In this short article, we will see how to create a simple log plot visualisation from one of the Volve Wells that was released as part of a larger dataset by Equinor in 2018.

I have previously covered different aspects of making these plots in the following articles:

For this tutorial, the notebook can be found here and the following video also accompanies it.

png
Well log plot created using the matplotlib Python library. Image by author.

Importing Libraries and Loading LAS Data

The first stage of any python project or notebook is to import the required libraries. In this case, we are going to be using lasio to load our las file, pandas for storing our well log data, and matplotlib for visualising our data.

import pandas as pd
import lasio
import matplotlib.pyplot as plt

To read the data we will use the lasio library which we explored in the previous notebook and video.

las = lasio.read("Data/15-9-19_SR_COMP.LAS")

Once the file has been loaded, we can check the contents of the file by using df.head() . This will return back the first five rows of our dataframe.

df = las.df()
df.head()
png
Well log plot created using the matplotlib Python library. Image by author.

We can see from the returned results that we have several columns of data, each column represents measurements that have been taken whilst the logging tools have been moved along the well.

The columns represent the following:

  • AC for acoustic compressional slowness
  • CALI for borehole caliper
  • DEN for bulk density
  • GR for gamma ray
  • NEU for neutron porosity
  • RDEP for deep resisitivity
  • RMED for medium resistivity

To make it easier to work with our dataframe, we can convert the dataframe index, which is set to depth, to a column within the dataframe. We can achieve this by resetting the index like so.

df.reset_index(inplace=True)

Note that inplace=True allows us to make the changes to the original dataframe object.

We can call upon df.head() again to ensure our new column has been created.

df.head()
png
Well log plot created using the matplotlib Python library. Image by author.

We also need to do a slight rename on the DEPT column and change it to DEPTH

df.rename(columns={'DEPT':'DEPTH'}, inplace=True)
df.head()
png
Well log plot created using the matplotlib Python library. Image by author.

Now that our data is in the right format, and the columns are correctly labeled, we can move onto generating our log plot.

Creating Well Log Plots with Matplotlib

Creating a Simple Line Plot

We can easily create a simple plot by calling upon df.plot() and passing in two of our columns

df.plot('GR', 'DEPTH')
png
Well log plot created using the matplotlib Python library. Image by author.

When we run this cell, we get a very simple plot that is hard to read and is also upside down.

Quick Subplot

If we want to view all of the columns within the dataframe, we can generate a subplot grid.

This is done by taking the same line as before (df.plot()), and instead of passing in curve names, we pass in subplots=True. We can also specify a figure size (figsize(), which controls how large the plot will appear.

df.plot(subplots=True, figsize=(15, 15))
png
Well log plot created using the matplotlib Python library. Image by author.

And we now see a grid of plots, one for each of the columns within the dataframe. This is a useful way to check where we have data and where we may have gaps.

However, we do not have much control over this plot. In the following sections we will see how we can start building up a well log plot with multiple measurements.

Working with Subplots in Matplotlib

There are many ways to generate subplots in using matplotlib. For this particular tutorial, we will work with subplot2grid.

fig = plt.subplots(figsize=(10,10))

#Set up the plot axis
ax1 = plt.subplot2grid((1,1), (0,0), rowspan=1, colspan = 1)

ax1.plot("GR", "DEPTH", data = df, color = "green") # Call the data from the df dataframe
ax1.set_xlabel("Gamma") # Assign a track title
ax1.set_xlim(0, 200) # Change the limits for the curve being plotted
ax1.set_ylim(4700, 3500) # Set the depth range
ax1.grid() # Display the grid

In the above code, we first have to define a figure (fig) and assign it to plt.subplots. Within the subplots arguments we can pass in the figure size, which we will set to 10 by 10.

Next, we need to create an axis on our figure object. We can do this by assigning ax1 is equal to plt.subplot2grid()..We first pass in the shape of the subplot grid, which for this example we will set 1 by 1.

Next we specify the location of our subplot using index positions. As we only have 1 subplot, we will set the location to zero, zero (0,0). Rowspan and colspan are both set to 1, which means they will only be 1 column wide by 1 row high.

Then, we need to tell matplotlib what we want to plot. In this case we are going use ax1.plot, and pass in our Gamma Ray and Depth columns.

Using the ax notation, we can further customise the plot by setting the x and y limits, and displaying the grid.

When we run this code we generate the following plot.

png
Well log plot created using the matplotlib Python library. Image by author.

Great! There we have a much better looking plot, and we can now start adding new tracks / subplots to our overall log plot.

Adding the Resistivity Log

To add a new track / subplot to our figure, we can repeat what we did above and add a new axis, ax2.

We then increment the second digit in the layout argument of plt.subplot2grid() so it is now 1 row, by 2 columns.

For ax2, we need to place it in the second subplot and this is done by changing the location argument from (0,0) to (0,1).

We then end up with:

ax1 = plt.subplot2grid((1,2), (0,0), rowspan=1, colspan = 1) 
ax2 = plt.subplot2grid((1,2), (0,1), rowspan=1, colspan = 1)

Also, as resistivity is normally scaled logarithmically, we need to add in the line: ax2.semilogx().

fig = plt.subplots(figsize=(10,10))

#Set up the plot axes
ax1 = plt.subplot2grid((1,2), (0,0), rowspan=1, colspan = 1)
ax2 = plt.subplot2grid((1,2), (0,1), rowspan=1, colspan = 1)


ax1.plot("GR", "DEPTH", data = df, color = "green") # Call the data from the well dataframe
ax1.set_xlabel("Gamma") # Assign a track title
ax1.set_xlim(0, 200) # Change the limits for the curve being plotted
ax1.set_ylim(4700, 3500) # Set the depth range
ax1.grid() # Display the grid

ax2.plot("RDEP", "DEPTH", data = df, color = "red")
ax2.set_xlabel("Deep Res.")
ax2.set_xlim(0.2, 2000)
ax2.semilogx()
ax2.set_ylim(4700, 3500)
ax2.grid()
png
Well log plot created using the matplotlib Python library. Image by author.

Adding the Density Log

To add a third track / subplot, we can repeat the above by adding in the density as a new subplot.

fig = plt.subplots(figsize=(10,10))

#Set up the plot axes
ax1 = plt.subplot2grid((1,3), (0,0), rowspan=1, colspan = 1)
ax2 = plt.subplot2grid((1,3), (0,1), rowspan=1, colspan = 1)
ax3 = plt.subplot2grid((1,3), (0,2), rowspan=1, colspan = 1)

ax1.plot("GR", "DEPTH", data = df, color = "green") # Call the data from the well dataframe
ax1.set_xlabel("Gamma") # Assign a track title
ax1.set_xlim(0, 200) # Change the limits for the curve being plotted
ax1.set_ylim(4700, 3500) # Set the depth range
ax1.grid() # Display the grid

ax2.plot("RDEP", "DEPTH", data = df, color = "red")
ax2.set_xlabel("Deep Res.")
ax2.set_xlim(0.2, 2000)
ax2.semilogx()
ax2.set_ylim(4700, 3500)
ax2.grid()

ax3.plot("DEN", "DEPTH", data = df, color = "red")
ax3.set_xlabel("Density")
ax3.set_xlim(1.95, 2.95)
ax3.set_ylim(4700, 3500)
ax3.grid()
png
Well log plot created using the matplotlib Python library. Image by author.

Tidying Up Common Elements

We can see from the previous code snippet that we have a number of elements that are repeated in each of the axis calls such as ax.set_ylim(4700, 3500). We can separate these out so that we only need to call these functions once. This saves on the amount of lines we need to write and makes the code more readable.

To achieve this, we add in a new for loop which will loop over the axes within fig.axes.

for i, ax in enumerate(fig.axes):
ax.set_ylim(4700, 3500) # Set the depth range
ax.grid()
ax.set_xlabel(curve_names[i])

Our final code, after separating out the common elements looks like this:

fig, axes = plt.subplots(figsize=(10,10))

curve_names = ['Gamma', 'Deep Res', 'Density']

#Set up the plot axes
ax1 = plt.subplot2grid((1,3), (0,0), rowspan=1, colspan = 1)
ax2 = plt.subplot2grid((1,3), (0,1), rowspan=1, colspan = 1)
ax3 = plt.subplot2grid((1,3), (0,2), rowspan=1, colspan = 1)

ax1.plot("GR", "DEPTH", data = df, color = "green")
ax1.set_xlim(0, 200)

ax2.plot("RDEP", "DEPTH", data = df, color = "red")
ax2.set_xlim(0.2, 2000)
ax2.semilogx()

ax3.plot("DEN", "DEPTH", data = df, color = "red")
ax3.set_xlim(1.95, 2.95)

for i, ax in enumerate(fig.axes):
ax.set_ylim(4700, 3500) # Set the depth range
ax.grid()
ax.set_xlabel(curve_names[i])
png
Well log plot created using the matplotlib Python library. Image by author.

Reducing Gaps Between Subplots

To tidy the plot up a little more, we can remove the depth labels in between each subplot / track and reduce the space between them. This is achieved by the looping over only ax2 and ax3, and by adjusting the width of the padding between plots.

#Hide tick labels on the y-axis 
for ax in [ax2, ax3]:
plt.setp(ax.get_yticklabels(), visible = False)

#Reduce the space between each subplot
fig.subplots_adjust(wspace = 0.05)

We then end up with the following:

fig, axes = plt.subplots(figsize=(10,10))

curve_names = ['Gamma', 'Deep Res', 'Density']

#Set up the plot axes
ax1 = plt.subplot2grid((1,3), (0,0), rowspan=1, colspan = 1)
ax2 = plt.subplot2grid((1,3), (0,1), rowspan=1, colspan = 1)
ax3 = plt.subplot2grid((1,3), (0,2), rowspan=1, colspan = 1)

#Set up the individual log tracks / subplots
ax1.plot("GR", "DEPTH", data = df, color = "green")
ax1.set_xlim(0, 200)

ax2.plot("RDEP", "DEPTH", data = df, color = "red")
ax2.set_xlim(0.2, 2000)
ax2.semilogx()

ax3.plot("DEN", "DEPTH", data = df, color = "red")
ax3.set_xlim(1.95, 2.95)

#Set up the common elements between the subplots
for i, ax in enumerate(fig.axes):
ax.set_ylim(4700, 3500) # Set the depth range
ax.grid()
ax.set_xlabel(curve_names[i])

#Hide tick labels on the y-axis
for ax in [ax2, ax3]:
plt.setp(ax.get_yticklabels(), visible = False)

#Reduce the space between each subplot
fig.subplots_adjust(wspace = 0.05)
png
Well log plot created using the matplotlib Python library. Image by author.

Adding a Secondary Axis to a Subplot for Neutron Porosity

It is standard to plot bulk density and neutron porosity on the same subplot / track. The interaction of these two curves allows us to identify lithology variations and hydrocarbon presence.

As the two measurements are on different units and scales (1.95 to 2.95 g/cc for Bulk Density and -15 to 60 for Neutron Porosity) we need to add another subplot on top using the twiny function. This allows us to use the same y-axis between the plots, but the x-axis can vary.

All labels have been moved to the top of the plot which is done by using ax.xaxis.set_ticks_position('top') and ax.xaxis.set_label_position('top').

We then need to modify the main for loop to check when we reach ax4 (which will be when i = 3, as Python indexes from 0) and then adjust the spine so that it sits above the Density label.

fig, axes = plt.subplots(figsize=(10,10))

curve_names = ['Gamma', 'Deep Res', 'Density', 'Neutron']


#Set up the plot axes
ax1 = plt.subplot2grid((1,3), (0,0), rowspan=1, colspan = 1)
ax2 = plt.subplot2grid((1,3), (0,1), rowspan=1, colspan = 1)
ax3 = plt.subplot2grid((1,3), (0,2), rowspan=1, colspan = 1)
ax4 = ax3.twiny()


#Set up the individual log tracks / subplots
ax1.plot("GR", "DEPTH", data = df, color = "green", lw = 0.5)
ax1.set_xlim(0, 200)

ax2.plot("RDEP", "DEPTH", data = df, color = "red", lw = 0.5)
ax2.set_xlim(0.2, 2000)
ax2.semilogx()

ax3.plot("DEN", "DEPTH", data = df, color = "red", lw = 0.5)
ax3.set_xlim(1.95, 2.95)

ax4.plot("NEU", "DEPTH", data = df, color = "blue", lw = 0.5)
ax4.set_xlim(45, -15)


#Set up the common elements between the subplots
for i, ax in enumerate(fig.axes):
ax.set_ylim(4700, 3500) # Set the depth range

ax.xaxis.set_ticks_position("top")
ax.xaxis.set_label_position("top")
ax.set_xlabel(curve_names[i])

if i == 3:
ax.spines["top"].set_position(("axes", 1.08))
else:
ax.grid()


#Hide tick labels on the y-axis
for ax in [ax2, ax3]:
plt.setp(ax.get_yticklabels(), visible = False)

#Reduce the space between each subplot
fig.subplots_adjust(wspace = 0.05)
png
Well log plot created using the matplotlib Python library. Image by author.

Summary

In this short tutorial, we have covered the basics of how to display a well log plot using matplotlib, how to add multiple tracks/subplots, and plot two curves on top of each other. Matplotlib provides a great way to build simple log plots from scratch and is a great library to learn.

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.

Similar Posts

One Comment

Leave a Reply

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