top of page

Generating Vs × Depth Profiles in a Simple Way with Python

  • Writer: Leonides Netto
    Leonides Netto
  • Aug 9
  • 4 min read

This tutorial is part of my series of practical resources for geoscientists. The goal is not to create overly sophisticated code, but rather to show how a simple script can solve a real problem in our daily work.


In this example, we will use a Python script to automatically plot shear-wave velocity (Vs) profiles from Multichannel Analysis of Surface Waves (MASW) data. However, this same logic can be applied to other types of geotechnical or geophysical profiles where you have depth and measurement pairs.


Why (I think!) this is useful:

  • If you perform multiple MASW tests, plotting each profile manually can be tedious.

  • This code reads a CSV file with Depth and Vs columns for each test and automatically generates clean, vertical Vs vs Depth plots.

  • It’s a straightforward tool — no need to be a programmer to use it.


What the script does:

  1. Reads your CSV file containing paired columns (Depth1, Vs1, Depth2, Vs2, …).

  2. Creates one subplot for each test automatically.

  3. Plots straight vertical segments for each Vs value across its depth interval (instead of connecting points with slanted lines).

  4. Saves the figure if you choose, or simply displays it.


It’s a quick and accessible way to transform tabular MASW data into clear visual profiles — perfect for reports, presentations, or preliminary analysis.


Python code:

First, I’ll walk you through the code step by step, and then I’ll share the complete script at the end.


I start by importing the two Python libraries I need for this workflow:

  • pandas – to read and manage the tabular CSV data containing depth and Vs values.

  • matplotlib.pyplot – to create and customize the Vs × Depth plots.


import pandas as pd

import matplotlib.pyplot as plt


Next, I point the script to my data file, in this case a CSV called maswb.csv stored on my desktop. The file contains paired columns for each test: Depth1, Vs1, Depth2, Vs2, … until Depth12, Vs12.


In Portuguese, caminho means the full path that tells the computer exactly where the file is located (for example, on your desktop, in a folder, or on another drive). In your case, the file name and location will probably be different, so you should replace this path with the one that matches your own CSV file.


caminho = r"C:\Users\leoni\OneDrive\Desktop\Mining\maswb.csv"

df = pd.read_csv(caminho)


I know in advance that my dataset contains 12 tests (n_ensaios = 12), so I store that number in a variable for later use in looping.


The word ensaios is Portuguese for “tests” or “experiments” — in this context, each ensaio is one shear-wave velocity (Vs) profile measured at a given location.


Then I create a figure with 4 rows and 3 columns of subplots, large enough to comfortably display each test separately. I also add spacing between plots using fig.subplots_adjust().


n_ensaios = 12

fig, axes = plt.subplots(nrows=4, ncols=3, figsize=(15, 18))

fig.subplots_adjust(hspace=0.4, wspace=0.3)


I then loop through all tests from 0 to 11. For each test, I calculate its row and column position in the subplot grid, and select the correct ax (subplot) to draw in.


for i in range(n_ensaios):

row = i // 3

col = i % 3

ax = axes[row, col]


Inside the loop, I construct the names of the depth and Vs columns dynamically.

For example, when i = 0 the column names will be "Depth1" and "Vs1".


depth_col = f"Depth{i+1}"

vs_col = f"Vs{i+1}"


Before plotting, I check if both columns exist in the DataFrame. If they do, I extract their values into depth and vs arrays.


if depth_col in df.columns and vs_col in df.columns:

depth = df[depth_col].values

vs = df[vs_col].values


The plotting happens in two steps for each profile:


  1. Vertical lines – I draw a thick blue vertical segment for each layer, from the top depth to the bottom depth, keeping the Vs value constant along that segment.


for j in range(len(depth) - 1):

ax.plot([vs[j], vs[j]], [depth[j], depth[j + 1]], linewidth=2, color='blue')


  1. Horizontal lines – I connect the base of one vertical segment to the base of the next one with a horizontal line. This creates a proper “step plot” look for the profile.


for j in range(len(depth) - 2):

profundidade = depth[j + 1]

vs_inicio = vs[j]

vs_fim = vs[j + 1]

ax.plot([vs_inicio, vs_fim], [profundidade, profundidade], linewidth=2, color='blue')


After plotting each profile, I invert the y-axis so that depth increases downward (as in a geotechnical log), set the subplot title, label the axes, and add a grid.


ax.invert_yaxis()

ax.set_title(f"Ensaio {i+1}")

ax.set_xlabel("Vs (m/s)")

ax.set_ylabel("Profundidade (m)")

ax.grid(True)


If a test’s columns are missing from the CSV, I hide that subplot and print a message indicating which columns were not found.


Finally, I tidy up the layout with plt.tight_layout() and display all plots with plt.show().


plt.tight_layout()


Example:

ree

Full code:


import pandas as pd

import matplotlib.pyplot as plt


# Caminho do arquivo CSV

caminho = r"C:\Users\leoni\OneDrive\Desktop\Mining\maswb.csv"


# Carrega os dados

df = pd.read_csv(caminho)


n_ensaios = 12


fig, axes = plt.subplots(nrows=4, ncols=3, figsize=(15, 18))

fig.subplots_adjust(hspace=0.4, wspace=0.3)


for i in range(n_ensaios):

row = i // 3

col = i % 3

ax = axes[row, col]


depth_col = f"Depth{i+1}"

vs_col = f"Vs{i+1}"


if depth_col in df.columns and vs_col in df.columns:

depth = df[depth_col].values

vs = df[vs_col].values


# desenha linhas verticais

for j in range(len(depth) - 1):

ax.plot([vs[j], vs[j]], [depth[j], depth[j + 1]], linewidth=2, color='blue')


# desenha linhas horizontais conectando o final de um traço vertical ao início do próximo

for j in range(len(depth) - 2):

# profundidade onde se conecta a linha horizontal (limite inferior da camada j)

profundidade = depth[j + 1]


# valores de Vs nas camadas j e j+1

vs_inicio = vs[j]

vs_fim = vs[j + 1]


ax.plot([vs_inicio, vs_fim], [profundidade, profundidade], linewidth=2, color='blue')


ax.invert_yaxis()

ax.set_title(f"Ensaio {i+1}")

ax.set_xlabel("Vs (m/s)")

ax.set_ylabel("Profundidade (m)")

ax.grid(True)

else:

ax.set_visible(False)

print(f"Colunas não encontradas: {vs_col} ou {depth_col}")


plt.tight_layout()

Comments


Contact
Information

Cities, Infrastructure and Environment Department

Institute for Technological Research of the State of São Paulo

Av. Prof. Almeida Prado 532 Cid. Universitária - Butantã
São Paulo, Brazil, 05508901

  • LinkedIn
  • Research Gate

Thanks for submitting!

©2023 by Leonides Guireli Netto. Powered and secured by Wix

bottom of page