ANALYSE DE CARTES
DE CRÉDIT À L'AIDE DE
SPARK ET SCALA
Iuliana Marin
Le contexte
• On s'attend à ce que les clients qui possèdent des cartes de crédit
remboursent leur solde mensuellement. Mais, parfois, ils ne
parviennent pas à payer la facture et par défaut (aucun paiement).
• Les banques doivent connaître l'analyse des risques des clients
défaillants, prévoir la probabilité de paiements futurs et agir en
conséquence.
• L'ensemble de données contient des informations sur les clients
pour l'historique des paiements des 6 derniers mois et leurs
attributs personnels.
Les données
• L'ensemble de données contient les informations suivantes:
Nom de colonne La description
CUSTID N ° de client
LIMIT_BAL Limite de dépense maximale pour le client
SEXE Forme
ÉDUCATION Valeurs: 1 (diplômé), 2 (université), 3 (lycée) et 4 (autres)
MARIAGE Valeurs: 1 (célibataire), 2 (marié) et 3 (autres)
ÂGE Âge du client
Statut de paiement pour les 6 derniers mois, une colonne pour chaque mois.
PAY_1 à PAY_6 Indique le nombre de mois (retard) que le client a mis pour payer la facture de ce
mois
BILL_AMT1 à BILL_AMT6 Le montant facturé pour la carte de crédit pour chacun des 6 derniers mois.
PAY_AMT1 à PAY_AMT6 Le montant réel que le client a payé pour chacun des 6 derniers mois
Si le client a fait défaut ou non le 7ème mois. Les valeurs sont 0 (pas par défaut) et
DEFAUT
1 (par défaut)
Des questions
• Y a-t-il une distinction claire entre les hommes et les femmes en ce
qui concerne le modèle de défaut de paiement?
• Comment l'état matrimonial et le niveau d'éducation affectent-ils le
niveau de défaut de paiement? Une catégorie de clients est-elle plus
défaillante que l'autre?
• Le délai de paiement moyen des 6 mois précédents indique-t-il que
le client fera défaut à l'avenir?
• => Construisez et comparez des modèles de prédiction qui
peuvent prédire si le client va par défaut le mois prochain en
fonction de son historique des 6 mois précédents.
Les données
• L'ensemble de données contient les informations suivantes:
Nom de colonne La description
CUSTID N ° de client
LIMIT_BAL Limite de dépense maximale pour le client
SEXE Forme
ÉDUCATION Valeurs: 1 (diplômé), 2 (université), 3 (lycée) et 4 (autres)
MARIAGE Valeurs: 1 (célibataire), 2 (marié) et 3 (autres)
ÂGE Âge du client
Statut de paiement pour les 6 derniers mois, une colonne pour chaque mois. Indique le nombre de mois (retard)
PAY_1 à PAY_6
que le client a mis pour payer la facture de ce mois
BILL_AMT1 à BILL_AMT6 Le montant facturé pour la carte de crédit pour chacun des 6 derniers mois.
PAY_AMT1 à PAY_AMT6 Le montant réel que le client a payé pour chacun des 6 derniers mois
DEFAUT Si le client a fait défaut ou non le 7ème mois. Les valeurs sont 0 (pas par défaut) et 1 (par défaut)
• 5 premiers enregistrements du fichier csv considéré.
CUSTID, LIMIT_BAL, SEXE, ÉDUCATION, MARIAGE, ÂGE, PAY_1, PAY_2, PAY_3, PAY_4, PAY_5, PAY_6, BILL_AMT1, BILL_AMT2, BILL_AMT3, BILL_AMT4, BILL_AMT5, BILL_AMTAM4,
BILL_AMT5, BILL_AMTAM4, PAY_TAM_TAM6, BILL_AMT5, BILL_AMTAM4, PAY_TAM_TAM6, PAY_TAM_TAM2
530,20000,2,2,2,21, -1, -1,2,2, -2, -2,0,0,0,0,0,0,0,0,0,0,0,162000, 0,0
38,60000,2,2,2,22,0,0,0,0, -2, -2,0,0,0,0,0,0,0,0,0,0,0,0,1576, 0
43,10000,1,2,2,22,0,0,0,0, -2, -2,0,0,0,0,0,0,0,0,0,0,0,0,1500, 0
47,20000,2,1,2,22,0,0,2, -1,0, -1,1131,291,582,291,0,291,291,582,0,0,130291,651,0
70,20000,1,4,2,22,2,0,0,0, -1, -1,1692,13250,433,1831,0,2891,13250,433,1831,0,2891,153504, 0
[Link] (1/5)
• Créez un nouveau projet Maven. Le contenu du fichier [Link]
est:
<project xmlns="[Link] xmlns:xsi="[Link]
xsi:schemaLocation="[Link] [Link]
<modelVersion>4.0.0</modelVersion>
<groupId>[Link]</groupId>
<artifactId>iuliana-spark-bda</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>${[Link]}</name>
<description>Spark + Scala : Credit Card Payment Analytics</description>
<properties>
<[Link]>1.8</[Link]>
<[Link]>1.8</[Link]>
<encoding>UTF-8</encoding>
<[Link]>2.11.5</[Link]>
<[Link]>2.11</[Link]>
</properties>
[Link] (2/5)
• Le contenu du fichier [Link] est:
<dependencies>
<dependency>
<groupId>[Link]-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${[Link]}</version>
</dependency>
<dependency>
<!-- Apache Spark main library -->
<groupId>[Link]</groupId>
<artifactId>spark-core_2.11</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spark-sql_2.11</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>[Link]</groupId>
<artifactId>spark-streaming_2.11</artifactId>
<version>2.0.0</version>
</dependency>
[Link] (3/5)
• Le contenu du fichier [Link] est:
<dependency>
<groupId>[Link]</groupId>
<artifactId>spark-mllib_2.11</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.specs2</groupId>
<artifactId>specs2-core_${[Link]}</artifactId>
<version>2.4.16</version>
<scope>test</scope>
</dependency>
[Link] (4/5)
• Le contenu du fichier [Link] est:
<dependency>
<groupId>[Link]</groupId>
<artifactId>spark-mllib_2.11</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.specs2</groupId>
<artifactId>specs2-core_${[Link]}</artifactId>
<version>2.4.16</version>
<scope>test</scope>
</dependency>
[Link] (5/5)
• Le contenu du fichier [Link] est:
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>[Link]</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>package</phase> <!-- bind to the packaging phase -->
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>[Link]</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
<configuration>
<useFile>false</useFile>
<disableXmlReport>true</disableXmlReport>
<includes>
<include>**/*Test.*</include>
<include>**/*Suite.*</include>
</includes>
</configuration>
</plugin>
</plugins>
</build>
</project>
Détails du système
• Créer un [Link] fichier dans lequel vous devez stocker les spécifications du
système.
package [Link]
object SparkCommonUtils {
import [Link]
import [Link]
import [Link]
val datadir = "C:\\Users\\Iuliana\\Documents\\Iuliana\\predare\\2020-2021\\Sem1\\TFBDA\\Lab12\\scala-spark-creditdefault\\data-files\\"
val appName="credit-default"
val sparkMasterURL = "local[2]"
val tempDir= "C:\\Users\\Iuliana\\Documents\\Iuliana\\predare\\2020-2021\\Sem1\\TFBDA\\Lab12\\scala-spark-creditdefault"
var spSession:SparkSession = null
var spContext:SparkContext = null
//create configuration object
val conf = new SparkConf()
.setAppName(appName)
.setMaster(sparkMasterURL)
.set("[Link]","2g")
.set("[Link]","2")
//create a spark context
spContext = [Link](conf)
//create a spark SQL session
spSession = SparkSession
.builder()
.appName(appName)
.master(sparkMasterURL)
.config("[Link]", tempDir)
.getOrCreate()
}
}
Importer les bibliothèques nécessaires
• Créer un [Link] fichier dans lequel vous implémenterez les solutions des questions
précédentes.
package [Link]
object CreditCardDefaulters extends App{
import [Link]._
import [Link]._
import [Link]
import [Link];
import [Link].{Vector, Vectors}
import [Link]
import [Link];
import [Link]._
import [Link]
import [Link]
import [Link]
import [Link]
import [Link]
[Link]("org").setLevel([Link]);
[Link]("akka").setLevel([Link]);
val spSession = [Link]
val spContext = [Link]
val datadir = [Link]
Charger CSV dans RDD
• Spark tourne autour du concept d'un ensemble de données distribué résilient (RDD), qui est une collection
d'éléments tolérants aux pannes pouvant être exploités en parallèle. Vous devrez charger le fichier CSV
dans un RDD. Vous devez également créer un schéma pour pouvoir charger dans l'ensemble de données.
println("loading data...")
val ccRDD1 = [Link](datadir + "[Link]")
[Link]()
[Link](5)
println("loaed lines : " + [Link]())
//remove the first line header and invalid lines like aaaaa
val ccRDD2 = [Link](x =>
!( [Link]("CUSTID") ||
[Link]("aaaaa")))
//creating schema to load into dataset
val schema =
StructType(
StructField("CustId", DoubleType, false) ::
StructField("LimitBal", DoubleType, false) ::
StructField("Sex", DoubleType, false) ::
StructField("Education", DoubleType, false) ::
StructField("Marriage", DoubleType, false) ::
StructField("Age", DoubleType, false) ::
StructField("AvgPayDur", DoubleType, false) ::
StructField("AvgBillAmt", DoubleType, false) ::
StructField("AvgPayAmt", DoubleType, false) ::
StructField("PerPaid", DoubleType, false) ::
StructField("Defaulted", DoubleType, false) :: Nil)
Gestion des enregistrements (1/4)
• Sur la base des données du fichier CSV, vous devez rechercher pour un client avec un certain identifiant
qui est son solde limite, son sexe, son éducation, son statut matrimonial, son âge, la durée moyenne de la
paie, le montant moyen facturé, le montant moyen du salaire pourcentage payé et s'ils appartiennent à la
catégorie de clients par défaut.
def transformToNumeric( inputStr : String) : Row = {
val attList=[Link](",")
//rounding of ages to immediate tens
val age:Double = [Link](attList(5).toDouble /10.0 ) * 10.0;
//normalizing sex to 1 & 2
val sex:Double = attList(2) match {
case "M" => 1.0
case "F" => 0.0
case _ => attList(2).toDouble
}
Gestion des enregistrements (2/4)
• Sur la base des données du fichier CSV, vous devez rechercher pour un client avec un certain identifiant
qui est son solde limite, son sexe, son éducation, son statut matrimonial, son âge, la durée moyenne de la
paie, le montant moyen facturé, le montant moyen du salaire pourcentage payé et s'ils appartiennent à la
catégorie de clients par défaut.
//finding average billed amount
val avgBillAmt:Double = [Link](
( attList(12).toDouble +
attList(13).toDouble + attList(14).toDouble + attList(15).toDouble + attList(16).toDouble +
attList(17).toDouble ) / 6.0 )
//finding average pay amount
val avgPayAmt:Double = [Link](
( attList(18).toDouble + attList(19).toDouble + attList(20).toDouble + attList(21).toDouble +
attList(22).toDouble +
attList(23).toDouble ) / 6.0 )
Gestion des enregistrements (3/4)
• Sur la base des données du fichier CSV, vous devez rechercher pour un client avec un certain identifiant
qui est son solde limite, son sexe, son éducation, son statut matrimonial, son âge, la durée moyenne de la
paie, le montant moyen facturé, le montant moyen du salaire pourcentage payé et s'ils appartiennent à la
catégorie de clients par défaut.
//finding average pay duration
val avgPayDuration:Double = [Link](
( attList(6).toDouble + attList(7).toDouble +
attList(8).toDouble +
attList(9).toDouble +
attList(10).toDouble +
attList(11).toDouble ) / 6.0 )
//finding percentage paid
var perPay:Double = [Link]((avgPayAmt/(avgBillAmt+1) * 100) / 25.0) * 25.0;
if ( perPay > 100) perPay=100
Gestion des enregistrements (4/4)
• Sur la base des données du fichier CSV, vous devez rechercher pour un client avec un certain identifiant
qui est son solde limite, son sexe, son éducation, son statut matrimonial, son âge, la durée moyenne de la
paie, le montant moyen facturé, le montant moyen du salaire pourcentage payé et s'ils appartiennent à la
catégorie de clients par défaut.
//removing not needed columns
val values= Row(attList(0).toDouble,
attList(1).toDouble,
sex, Les cinq premières premières lignes sont obtenues
attList(3).toDouble,
attList(4).toDouble, à l'aide du code:
age,
avgPayDuration,
avgBillAmt, val ccVectors = [Link](transformToNumeric)
avgPayAmt, [Link]()
perPay, val ccDf1 = [Link](ccVectors, schema)
attList(24).toDouble [Link]()
) [Link](5)
return values
}
DataFrames
• Créer DataFrames concernant le sexe, l’éducation et le mariage qui seront utilisés pour une analyse plus
approfondie.
//creating dataframe for gender
val genderList = Array("{'sexName': 'Male', 'sexId': '1.0'}",
"{ 'sexName':'Female','sexId':'2.0' }")
val genderDf = [Link]([Link](genderList))
val ccDf2 = [Link](genderDf, ccDf1("Sex") === genderDf("sexId"))
.drop("sexId").repartition(2)
//creating dataframe for education
val eduList = Array("{'eduName': 'Graduate', 'eduId': '1.0'}",
"{'eduName': 'University', 'eduId': '2.0'}",
"{'eduName': 'High School', 'eduId': '3.0'}",
"{'eduName': 'Others', 'eduId': '4.0'}")
val eduDf = [Link]([Link](eduList))
val ccDf3 = [Link](eduDf, ccDf1("Education") === eduDf("eduId"))
.drop("eduId").repartition(2)
//creating dataframe for marriage
val marriageList = Array("{'marriageName': 'Single', 'marriageId': '1.0'}",
"{'marriageName': 'Married', 'marriageId': '2.0'}",
"{'marriageName': 'Others', 'marriageId': '3.0'}")
val marriageDf = [Link]([Link](marriageList))
val ccDf4 = [Link](marriageDf, ccDf1("Marriage") === marriageDf("marriageId"))
.drop("marriageId").repartition(2)
println("Final Data frame :")
[Link]()
[Link](5)
Répondre aux questions
• Y a-t-il une distinction claire entre les hommes et les femmes en ce qui concerne le modèle de défaut de
paiement?
[Link]("CCDATA")
//distinction between males and females in defaulting
val Male_Female_Diff = [Link](
"SELECT sexName, count(*) as Total, " +
" SUM(Defaulted) as Defaults, " +
" ROUND(SUM(Defaulted) * 100 / count(*)) as PerDefault " +
" FROM CCDATA GROUP BY sexName" );
println("output for Male_Female_Diff")
Male_Female_Diff.show()
Répondre aux questions
• Comment l'état matrimonial et le niveau d'éducation affectent-ils le niveau de défaut de paiement?
//does marital status affect defaulting?
val Martial_Correlation = [Link](
"SELECT marriageName, eduName, count(*) as Total," +
" SUM(Defaulted) as Defaults, " +
" ROUND(SUM(Defaulted) * 100 / count(*)) as PerDefault " +
" FROM CCDATA GROUP BY marriageName, eduName " +
" ORDER BY 1,2");
println("output for Marital_Correlation")
Martial_Correlation.show()
Répondre aux questions
• Le délai de paiement moyen des 6 mois précédents indique-t-il que le client fera défaut à l'avenir?
//indication of future default on basis of prev 6 months
val Future_Prob = [Link](
"SELECT AvgPayDur, count(*) as Total, " +
" SUM(Defaulted) as Defaults, " +
" ROUND(SUM(Defaulted) * 100 / count(*)) as PerDefault " +
" FROM CCDATA GROUP BY AvgPayDur ORDER BY 1");
println("output for Future_Prob")
Future_Prob.show()
Répondre aux questions
• Pour en savoir plus sur les prédictions, des algorithmes d'apprentissage automatique sont utilisés. Pour cela,
les données doivent être transformées en vecteurs d'étiquettes.
def transformToLabelVectors(inStr : Row ) : LabeledPoint = {
val labelVectors = new LabeledPoint([Link](0) ,
[Link]([Link](2),
[Link](3),
[Link](4),
[Link](5),
[Link](6),
[Link](7),
[Link](8),
[Link](9)));
return labelVectors
}
val ccRDD3 = [Link](2);
val ccLabelVectors = [Link](transformToLabelVectors)
[Link]()
val ccDf5 = [Link](ccLabelVectors, classOf[LabeledPoint] )
[Link]()
val ccMap = [Link]("CustId", "Defaulted")
val ccDf6 = [Link](ccMap, ccDf5("label") === ccMap("CustId"))
.drop("label").repartition(2)
println("final data: ")
[Link]()
Répondre aux questions
• Les données doivent être divisées en formation et en tests. Le classificateur d'arbres de décision, les forêts
aléatoires et les bayes naïves sont utilisés comme algorithmes d'apprentissage automatique.
Répondre aux questions
• Les données doivent être divisées en formation et en tests. Le classificateur d'arbres de décision, les forêts
aléatoires et les bayes naïves sont utilisés comme algorithmes d'apprentissage automatique.
//splitting data for training and testing
val Array(trainingData, testData) = [Link](Array(0.7, 0.3))
[Link]()
[Link]()
// >> To predict defaults. comparing multiple algorithms for the best results <<
val evaluator = new MulticlassClassificationEvaluator()
[Link]("Prediction")
[Link]("Defaulted")
[Link]("accuracy")
// >> Decision Trees Classifier <<
val dtClassifier = new DecisionTreeClassifier()
[Link]("Defaulted")
[Link]("Prediction")
[Link](4)
val dtModel = [Link](trainingData)
val dtPredictions = [Link](testData)
println("\nDecision Trees Accuracy = " + [Link](dtPredictions))
Répondre aux questions
• Les données doivent être divisées en formation et en tests. Le classificateur d'arbres de décision, les forêts
aléatoires et les bayes naïves sont utilisés comme algorithmes d'apprentissage automatique.
// >> Random Forests <<
val rfClassifier = new RandomForestClassifier()
[Link]("Defaulted")
[Link]("Prediction")
val rfModel = [Link](trainingData)
val rfPredictions = [Link](testData)
println("\nRandom Forests Accuracy = " + [Link](rfPredictions))
Répondre aux questions
• Les données doivent être divisées en formation et en tests. Le classificateur d'arbres de décision, les forêts
aléatoires et les bayes naïves sont utilisés comme algorithmes d'apprentissage automatique.
// >> Naive Bayes <<
val nbClassifier = new NaiveBayes()
[Link]("Defaulted")
[Link]("Prediction")
val nbModel = [Link](trainingData)
val nbPredictions = [Link](testData)
println("\nNaive Bayes Accuracy = " + [Link](nbPredictions))
Clustering des données
• Les données peuvent être regroupées en 4 groupes basés sur l'âge, l'éducation, le sexe et le mariage.
val ccDf7 = [Link]("Sex","Education","Marriage","Age","CustId")
[Link]()
//centering and scaling
val meanVal = [Link](avg("Sex"), avg("Education"),avg("Marriage"),
avg("Age")).collectAsList().get(0)
val stdVal = [Link](stddev("Sex"), stddev("Education"),
stddev("Marriage"),stddev("Age")).collectAsList().get(0)
val bcMeans=[Link](meanVal)
val bcStdDev=[Link](stdVal)
def centerAndScale(inRow : Row ) : LabeledPoint = {
val meanArray=[Link]
val stdArray=[Link]
var retArray=Array[Double]()
for (i <- 0 to [Link] - 2) {
val csVal = ( [Link](i) - [Link](i)) /
[Link](i)
retArray = retArray :+ csVal
}
return new LabeledPoint([Link](4),[Link](retArray))
}
val ccRDD4 = [Link](2);
val ccRDD5 = [Link](centerAndScale)
[Link]()
val ccDf8 = [Link](ccRDD5, classOf[LabeledPoint] )
[Link]("label","features").show(10)
K signifie méthode
• La méthode k signifie est appliquée à nos données.
val kmeans = new KMeans()
[Link](4)
[Link](1L)
val model = [Link](ccDf8)
val predictions = [Link](ccDf8)
println("Groupings :")
[Link]("prediction").count().show()
println("Customer Assignments :")
[Link]("label","prediction").show()
Conclusions
• Spark peut être utilisé pour analyser les données.
• Il peut être prévu la probabilité d'actions futures.
• Des algorithmes d'apprentissage automatique ont été appliqués
pour prédire si un client va faire défaut le mois prochain en
fonction de son historique correspondant aux 6 derniers mois.