diff --git a/tutorials/CMakeLists.txt b/tutorials/CMakeLists.txt
index e459d54837e8fab883f31a1eea8b46a8b2116c5a..7f59dcdf28ba9805539c3be404ace7c949d7702c 100644
--- a/tutorials/CMakeLists.txt
+++ b/tutorials/CMakeLists.txt
@@ -402,3 +402,10 @@ if(ROOT_python_FOUND)
                 ENVIRONMENT ${ROOT_environ})
   endforeach()
 endif()
+
+#---Download files for tutorials---------
+set(rootsite http://root.cern.ch/files)
+set(csvtimeout 60)
+set (tdf014_csv tdf014_CsvDataSource_MuRun2010B.csv)
+file(DOWNLOAD ${rootsite}/tutorials/${tdf014_csv} ${CMAKE_CURRENT_BINARY_DIR}/${tdf014_csv} TIMEOUT ${csvtimeout})
+
diff --git a/tutorials/dataframe/tdf014_CSVDataSource.C b/tutorials/dataframe/tdf014_CSVDataSource.C
new file mode 100644
index 0000000000000000000000000000000000000000..57b4a415f6889796cccf264c851bf538cf9bfc90
--- /dev/null
+++ b/tutorials/dataframe/tdf014_CSVDataSource.C
@@ -0,0 +1,71 @@
+/// \file
+/// \ingroup tutorial_tdataframe
+/// \notebook
+/// This tutorial illustrates how use the TDataFrame in combination with a
+/// TDataSource. In this case we use a TCsvDS. This data source allows to read
+/// a CSV file from a TDataFrame.
+/// As a result of running this tutorial, we will produce plots of the dimuon
+/// spectrum starting from a subset of the CMS collision events of Run2010B.
+/// Dataset Reference:
+/// McCauley, T. (2014). Dimuon event information derived from the Run2010B
+/// public Mu dataset. CERN Open Data Portal.
+/// DOI: [10.7483/OPENDATA.CMS.CB8H.MFFA](http://opendata.cern.ch/record/700).
+///
+/// \macro_code
+///
+/// \date October 2017
+/// \author Enric Tejedor
+
+int tdf014_CSVDataSource()
+{
+   // Let's first create a TDF that will read from the CSV file.
+   // The types of the columns will be automatically inferred.
+   auto fileName = "tdf014_CsvDataSource_MuRun2010B.csv";
+   auto tdf = ROOT::Experimental::TDF::MakeCsvDataFrame(fileName);
+
+   // Now we will apply a first filter based on two columns of the CSV,
+   // and we will define a new column that will contain the invariant mass.
+   // Note how the new invariant mass column is defined from several other
+   // columns that already existed in the CSV file.
+   auto filteredEvents =
+      tdf.Filter("Q1 * Q2 == -1")
+         .Define("m", "sqrt(pow(E1 + E2, 2) - (pow(px1 + px2, 2) + pow(py1 + py2, 2) + pow(pz1 + pz2, 2)))");
+
+   // Next we create a histogram to hold the invariant mass values and we draw it.
+   auto invMass =
+      filteredEvents.Histo1D({"invMass", "CMS Opendata: #mu#mu mass;#mu#mu mass [GeV];Events", 512, 2, 110}, "m");
+
+   auto c = new TCanvas();
+   c->SetLogx();
+   c->SetLogy();
+   invMass->DrawClone();
+
+   // We will now produce a plot also for the J/Psi particle. We will plot
+   // on the same canvas the full spectrum and the zoom in the J/psi particle.
+   // First we will create the full spectrum histogram from the invariant mass
+   // column, using a different histogram model than before.
+   auto fullSpectrum =
+      filteredEvents.Histo1D({"Spectrum", "Subset of CMS Run 2010B;#mu#mu mass [GeV];Events", 1024, 2, 110}, "m");
+
+   // Next we will create the histogram for the J/psi particle, applying first
+   // the corresponding cut.
+   double jpsiLow = 2.95;
+   double jpsiHigh = 3.25;
+   auto jpsiCut = [jpsiLow, jpsiHigh](double m) { return m < jpsiHigh && m > jpsiLow; };
+   auto jpsi =
+      filteredEvents.Filter(jpsiCut, {"m"})
+         .Histo1D({"jpsi", "Subset of CMS Run 2010B: J/#psi window;#mu#mu mass [GeV];Events", 128, jpsiLow, jpsiHigh},
+                  "m");
+
+   // Finally we draw the two histograms side by side.
+   auto dualCanvas = new TCanvas("DualCanvas", "DualCanvas", 800, 512);
+   dualCanvas->Divide(2, 1);
+   auto leftPad = dualCanvas->cd(1);
+   leftPad->SetLogx();
+   leftPad->SetLogy();
+   fullSpectrum->DrawClone("Hist");
+   dualCanvas->cd(2);
+   jpsi->DrawClone("HistP");
+
+   return 0;
+}
diff --git a/tutorials/dataframe/tdf014_CSVDataSource.py b/tutorials/dataframe/tdf014_CSVDataSource.py
new file mode 100644
index 0000000000000000000000000000000000000000..876c83e2290c307d46c15ffd1f5a65db1c4c2560
--- /dev/null
+++ b/tutorials/dataframe/tdf014_CSVDataSource.py
@@ -0,0 +1,64 @@
+## \file
+## \ingroup tutorial_tdataframe
+## \notebook
+## This tutorial illustrates how use the TDataFrame in combination with a
+## TDataSource. In this case we use a TCsvDS. This data source allows to read
+## a CSV file from a TDataFrame.
+## As a result of running this tutorial, we will produce plots of the dimuon
+## spectrum starting from a subset of the CMS collision events of Run2010B.
+## Dataset Reference:
+## McCauley, T. (2014). Dimuon event information derived from the Run2010B
+## public Mu dataset. CERN Open Data Portal.
+## DOI: [10.7483/OPENDATA.CMS.CB8H.MFFA](http://opendata.cern.ch/record/700).
+##
+## \macro_code
+##
+## \date October 2017
+## \author Enric Tejedor
+
+import ROOT
+
+# Let's first create a TDF that will read from the CSV file.
+# The types of the columns will be automatically inferred.
+fileName = "tdf014_CsvDataSource_MuRun2010B.csv"
+MakeCsvDataFrame = ROOT.ROOT.Experimental.TDF.MakeCsvDataFrame
+tdf = MakeCsvDataFrame(fileName)
+
+# Now we will apply a first filter based on two columns of the CSV,
+# and we will define a new column that will contain the invariant mass.
+# Note how the new invariant mass column is defined from several other
+# columns that already existed in the CSV file.
+filteredEvents = tdf.Filter("Q1 * Q2 == -1") \
+                    .Define("m", "sqrt(pow(E1 + E2, 2) - (pow(px1 + px2, 2) + pow(py1 + py2, 2) + pow(pz1 + pz2, 2)))")
+
+# Next we create a histogram to hold the invariant mass values and we draw it.
+invMass = filteredEvents.Histo1D(("invMass", "CMS Opendata: #mu#mu mass;#mu#mu mass [GeV];Events", 512, 2, 110), "m")
+
+c = ROOT.TCanvas()
+c.SetLogx()
+c.SetLogy()
+invMass.Draw()
+
+# We will now produce a plot also for the J/Psi particle. We will plot
+# on the same canvas the full spectrum and the zoom in the J/psi particle.
+# First we will create the full spectrum histogram from the invariant mass
+# column, using a different histogram model than before.
+fullSpectrum = filteredEvents.Histo1D(("Spectrum", "Subset of CMS Run 2010B;#mu#mu mass [GeV];Events", 1024, 2, 110), "m")
+
+# Next we will create the histogram for the J/psi particle, applying first
+# the corresponding cut.
+jpsiLow = 2.95
+jpsiHigh = 3.25
+jpsiCut = 'm < %s && m > %s' % (jpsiHigh, jpsiLow)
+jpsi = filteredEvents.Filter(jpsiCut) \
+                     .Histo1D(("jpsi", "Subset of CMS Run 2010B: J/#psi window;#mu#mu mass [GeV];Events", 128, jpsiLow, jpsiHigh), "m")
+
+# Finally we draw the two histograms side by side.
+dualCanvas = ROOT.TCanvas("DualCanvas", "DualCanvas", 800, 512)
+dualCanvas.Divide(2, 1)
+leftPad = dualCanvas.cd(1)
+leftPad.SetLogx()
+leftPad.SetLogy()
+fullSpectrum.Draw("Hist")
+dualCanvas.cd(2)
+jpsi.Draw("HistP")