Transform a dataframe to a transaction object for the apriori function without exporting and reloading the...












1















I'm getting in trouble transforming a dataframe object into a transaction object.
I create a dataframe grouped by InvoiceNumber and the list of products separated by ',' (the dataframe then contains two columns), everything is ok,



df = read.csv('Orders.csv', sep = ';', stringsAsFactors = T)
df$Document.Date = as.Date(df$Document.Date, format = '%d/%m/%Y')

library(tidyverse)
library(plyr)

grouping_for_AA =
data.frame(
df %>%
group_by(Sales.Document, Material) %>%
dplyr::select(Sales.Document, Material, Document.Date)
)


#Create transaction data building a list of material for each sales doc
#separated by a ,
transactionData = ddply(grouping_for_AA, c('Sales.Document'),
function(df) paste(df$Material,
collapse = ',')
)


but when I use the as(data, 'transactions') function R say me to discretize input, so I use as.factor for the Product list column, but doing this each transaction becomes a factor level and no rules can be mined (clearly).



#set column InvoiceNo of dataframe transactionData  
transactionData$Sales.Document <- NULL
#Change name of lists of Materials
colnames(transactionData) = 'Material'

#transform to factor
transactionData = data.frame(lapply(transactionData, factor))


#Create a transaction object: errors can be due to the package containing 'as'
trObj <- as(transactionData, "transactions")


I already tried dataframes in single and basket format, but I could not solve it.



Any Idea on how to transform a dataframe into transaction format without exporting and reloading data?










share|improve this question

























  • Yes, but it's much hard without having some of your data. Also fakes data are ok if you cannot publish yours. Can you post them?

    – s_t
    Nov 21 '18 at 14:22











  • Here some fake data: fake_data = data.frame(Sales.Document = c(1,1,1,2,2,3), Material = as.factor(c('A','B','C', 'A', 'C', 'A')))

    – Nicolò Manca
    Nov 21 '18 at 16:30


















1















I'm getting in trouble transforming a dataframe object into a transaction object.
I create a dataframe grouped by InvoiceNumber and the list of products separated by ',' (the dataframe then contains two columns), everything is ok,



df = read.csv('Orders.csv', sep = ';', stringsAsFactors = T)
df$Document.Date = as.Date(df$Document.Date, format = '%d/%m/%Y')

library(tidyverse)
library(plyr)

grouping_for_AA =
data.frame(
df %>%
group_by(Sales.Document, Material) %>%
dplyr::select(Sales.Document, Material, Document.Date)
)


#Create transaction data building a list of material for each sales doc
#separated by a ,
transactionData = ddply(grouping_for_AA, c('Sales.Document'),
function(df) paste(df$Material,
collapse = ',')
)


but when I use the as(data, 'transactions') function R say me to discretize input, so I use as.factor for the Product list column, but doing this each transaction becomes a factor level and no rules can be mined (clearly).



#set column InvoiceNo of dataframe transactionData  
transactionData$Sales.Document <- NULL
#Change name of lists of Materials
colnames(transactionData) = 'Material'

#transform to factor
transactionData = data.frame(lapply(transactionData, factor))


#Create a transaction object: errors can be due to the package containing 'as'
trObj <- as(transactionData, "transactions")


I already tried dataframes in single and basket format, but I could not solve it.



Any Idea on how to transform a dataframe into transaction format without exporting and reloading data?










share|improve this question

























  • Yes, but it's much hard without having some of your data. Also fakes data are ok if you cannot publish yours. Can you post them?

    – s_t
    Nov 21 '18 at 14:22











  • Here some fake data: fake_data = data.frame(Sales.Document = c(1,1,1,2,2,3), Material = as.factor(c('A','B','C', 'A', 'C', 'A')))

    – Nicolò Manca
    Nov 21 '18 at 16:30
















1












1








1


1






I'm getting in trouble transforming a dataframe object into a transaction object.
I create a dataframe grouped by InvoiceNumber and the list of products separated by ',' (the dataframe then contains two columns), everything is ok,



df = read.csv('Orders.csv', sep = ';', stringsAsFactors = T)
df$Document.Date = as.Date(df$Document.Date, format = '%d/%m/%Y')

library(tidyverse)
library(plyr)

grouping_for_AA =
data.frame(
df %>%
group_by(Sales.Document, Material) %>%
dplyr::select(Sales.Document, Material, Document.Date)
)


#Create transaction data building a list of material for each sales doc
#separated by a ,
transactionData = ddply(grouping_for_AA, c('Sales.Document'),
function(df) paste(df$Material,
collapse = ',')
)


but when I use the as(data, 'transactions') function R say me to discretize input, so I use as.factor for the Product list column, but doing this each transaction becomes a factor level and no rules can be mined (clearly).



#set column InvoiceNo of dataframe transactionData  
transactionData$Sales.Document <- NULL
#Change name of lists of Materials
colnames(transactionData) = 'Material'

#transform to factor
transactionData = data.frame(lapply(transactionData, factor))


#Create a transaction object: errors can be due to the package containing 'as'
trObj <- as(transactionData, "transactions")


I already tried dataframes in single and basket format, but I could not solve it.



Any Idea on how to transform a dataframe into transaction format without exporting and reloading data?










share|improve this question
















I'm getting in trouble transforming a dataframe object into a transaction object.
I create a dataframe grouped by InvoiceNumber and the list of products separated by ',' (the dataframe then contains two columns), everything is ok,



df = read.csv('Orders.csv', sep = ';', stringsAsFactors = T)
df$Document.Date = as.Date(df$Document.Date, format = '%d/%m/%Y')

library(tidyverse)
library(plyr)

grouping_for_AA =
data.frame(
df %>%
group_by(Sales.Document, Material) %>%
dplyr::select(Sales.Document, Material, Document.Date)
)


#Create transaction data building a list of material for each sales doc
#separated by a ,
transactionData = ddply(grouping_for_AA, c('Sales.Document'),
function(df) paste(df$Material,
collapse = ',')
)


but when I use the as(data, 'transactions') function R say me to discretize input, so I use as.factor for the Product list column, but doing this each transaction becomes a factor level and no rules can be mined (clearly).



#set column InvoiceNo of dataframe transactionData  
transactionData$Sales.Document <- NULL
#Change name of lists of Materials
colnames(transactionData) = 'Material'

#transform to factor
transactionData = data.frame(lapply(transactionData, factor))


#Create a transaction object: errors can be due to the package containing 'as'
trObj <- as(transactionData, "transactions")


I already tried dataframes in single and basket format, but I could not solve it.



Any Idea on how to transform a dataframe into transaction format without exporting and reloading data?







r data-mining apriori arules






share|improve this question















share|improve this question













share|improve this question




share|improve this question








edited Nov 21 '18 at 19:20









s_t

3,25821031




3,25821031










asked Nov 21 '18 at 11:50









Nicolò MancaNicolò Manca

102




102













  • Yes, but it's much hard without having some of your data. Also fakes data are ok if you cannot publish yours. Can you post them?

    – s_t
    Nov 21 '18 at 14:22











  • Here some fake data: fake_data = data.frame(Sales.Document = c(1,1,1,2,2,3), Material = as.factor(c('A','B','C', 'A', 'C', 'A')))

    – Nicolò Manca
    Nov 21 '18 at 16:30





















  • Yes, but it's much hard without having some of your data. Also fakes data are ok if you cannot publish yours. Can you post them?

    – s_t
    Nov 21 '18 at 14:22











  • Here some fake data: fake_data = data.frame(Sales.Document = c(1,1,1,2,2,3), Material = as.factor(c('A','B','C', 'A', 'C', 'A')))

    – Nicolò Manca
    Nov 21 '18 at 16:30



















Yes, but it's much hard without having some of your data. Also fakes data are ok if you cannot publish yours. Can you post them?

– s_t
Nov 21 '18 at 14:22





Yes, but it's much hard without having some of your data. Also fakes data are ok if you cannot publish yours. Can you post them?

– s_t
Nov 21 '18 at 14:22













Here some fake data: fake_data = data.frame(Sales.Document = c(1,1,1,2,2,3), Material = as.factor(c('A','B','C', 'A', 'C', 'A')))

– Nicolò Manca
Nov 21 '18 at 16:30







Here some fake data: fake_data = data.frame(Sales.Document = c(1,1,1,2,2,3), Material = as.factor(c('A','B','C', 'A', 'C', 'A')))

– Nicolò Manca
Nov 21 '18 at 16:30














1 Answer
1






active

oldest

votes


















0














You can try this, to convert your data.frame in a transaction dataset. I've added a fake date, but I think it's useless, due you are not using it in your elaboration:



data$Document.Date <- Sys.Date()
data
Sales.Document Material Document.Date
1 1 A 2018-11-21
2 1 B 2018-11-21
3 1 C 2018-11-21
4 2 A 2018-11-21
5 2 C 2018-11-21
6 3 A 2018-11-21


Now exactly your dataset: you can add data.frame() in the dplyr chain:



library(tidyverse)
library(plyr)
grouping_for_AA <- data %>%
group_by(Sales.Document, Material) %>%
dplyr::select(Sales.Document, Material, Document.Date) %>%
data.frame()


Now you can transform in a transactions data:



library(arules)
library(reshape2)
trans <- as(split(grouping_for_AA[,"Material"], grouping_for_AA[,"Sales.Document"]), "transactions")

inspect(trans)
items transactionID
[1] {A,B,C} 1
[2] {A,C} 2
[3] {A} 3


Lastly, you can apply the apriori() function:



rules <- apriori(trans, parameter = list(supp = 0.3, conf = 0.3, target="rules", minlen=2)) 
inspect(rules)
lhs rhs support confidence lift count
[1] {B} => {C} 0.3333333 1.0000000 1.5 1
[2] {C} => {B} 0.3333333 0.5000000 1.5 1
[3] {B} => {A} 0.3333333 1.0000000 1.0 1
[4] {A} => {B} 0.3333333 0.3333333 1.0 1
[5] {C} => {A} 0.6666667 1.0000000 1.0 2
[6] {A} => {C} 0.6666667 0.6666667 1.0 2
[7] {B,C} => {A} 0.3333333 1.0000000 1.0 1
[8] {A,B} => {C} 0.3333333 1.0000000 1.5 1
[9] {A,C} => {B} 0.3333333 0.5000000 1.5 1





share|improve this answer
























  • It works perfectly! Thank you very much!

    – Nicolò Manca
    Nov 22 '18 at 8:40











  • @NicolòManca please consider this, there is no obligation in it, but it points out to a wider community that the answer has a solution.

    – s_t
    Nov 22 '18 at 8:42













  • I cant vote cause i have less than 15 reputation point :(

    – Nicolò Manca
    Nov 22 '18 at 10:40











  • Sure thank you =)

    – Nicolò Manca
    Nov 22 '18 at 17:13











  • Just for reference. Look at the manual page ? transactions. Example 4 fits this case.

    – Michael Hahsler
    Nov 25 '18 at 16:25











Your Answer






StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");

StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});


}
});














draft saved

draft discarded


















StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53411428%2ftransform-a-dataframe-to-a-transaction-object-for-the-apriori-function-without-e%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









0














You can try this, to convert your data.frame in a transaction dataset. I've added a fake date, but I think it's useless, due you are not using it in your elaboration:



data$Document.Date <- Sys.Date()
data
Sales.Document Material Document.Date
1 1 A 2018-11-21
2 1 B 2018-11-21
3 1 C 2018-11-21
4 2 A 2018-11-21
5 2 C 2018-11-21
6 3 A 2018-11-21


Now exactly your dataset: you can add data.frame() in the dplyr chain:



library(tidyverse)
library(plyr)
grouping_for_AA <- data %>%
group_by(Sales.Document, Material) %>%
dplyr::select(Sales.Document, Material, Document.Date) %>%
data.frame()


Now you can transform in a transactions data:



library(arules)
library(reshape2)
trans <- as(split(grouping_for_AA[,"Material"], grouping_for_AA[,"Sales.Document"]), "transactions")

inspect(trans)
items transactionID
[1] {A,B,C} 1
[2] {A,C} 2
[3] {A} 3


Lastly, you can apply the apriori() function:



rules <- apriori(trans, parameter = list(supp = 0.3, conf = 0.3, target="rules", minlen=2)) 
inspect(rules)
lhs rhs support confidence lift count
[1] {B} => {C} 0.3333333 1.0000000 1.5 1
[2] {C} => {B} 0.3333333 0.5000000 1.5 1
[3] {B} => {A} 0.3333333 1.0000000 1.0 1
[4] {A} => {B} 0.3333333 0.3333333 1.0 1
[5] {C} => {A} 0.6666667 1.0000000 1.0 2
[6] {A} => {C} 0.6666667 0.6666667 1.0 2
[7] {B,C} => {A} 0.3333333 1.0000000 1.0 1
[8] {A,B} => {C} 0.3333333 1.0000000 1.5 1
[9] {A,C} => {B} 0.3333333 0.5000000 1.5 1





share|improve this answer
























  • It works perfectly! Thank you very much!

    – Nicolò Manca
    Nov 22 '18 at 8:40











  • @NicolòManca please consider this, there is no obligation in it, but it points out to a wider community that the answer has a solution.

    – s_t
    Nov 22 '18 at 8:42













  • I cant vote cause i have less than 15 reputation point :(

    – Nicolò Manca
    Nov 22 '18 at 10:40











  • Sure thank you =)

    – Nicolò Manca
    Nov 22 '18 at 17:13











  • Just for reference. Look at the manual page ? transactions. Example 4 fits this case.

    – Michael Hahsler
    Nov 25 '18 at 16:25
















0














You can try this, to convert your data.frame in a transaction dataset. I've added a fake date, but I think it's useless, due you are not using it in your elaboration:



data$Document.Date <- Sys.Date()
data
Sales.Document Material Document.Date
1 1 A 2018-11-21
2 1 B 2018-11-21
3 1 C 2018-11-21
4 2 A 2018-11-21
5 2 C 2018-11-21
6 3 A 2018-11-21


Now exactly your dataset: you can add data.frame() in the dplyr chain:



library(tidyverse)
library(plyr)
grouping_for_AA <- data %>%
group_by(Sales.Document, Material) %>%
dplyr::select(Sales.Document, Material, Document.Date) %>%
data.frame()


Now you can transform in a transactions data:



library(arules)
library(reshape2)
trans <- as(split(grouping_for_AA[,"Material"], grouping_for_AA[,"Sales.Document"]), "transactions")

inspect(trans)
items transactionID
[1] {A,B,C} 1
[2] {A,C} 2
[3] {A} 3


Lastly, you can apply the apriori() function:



rules <- apriori(trans, parameter = list(supp = 0.3, conf = 0.3, target="rules", minlen=2)) 
inspect(rules)
lhs rhs support confidence lift count
[1] {B} => {C} 0.3333333 1.0000000 1.5 1
[2] {C} => {B} 0.3333333 0.5000000 1.5 1
[3] {B} => {A} 0.3333333 1.0000000 1.0 1
[4] {A} => {B} 0.3333333 0.3333333 1.0 1
[5] {C} => {A} 0.6666667 1.0000000 1.0 2
[6] {A} => {C} 0.6666667 0.6666667 1.0 2
[7] {B,C} => {A} 0.3333333 1.0000000 1.0 1
[8] {A,B} => {C} 0.3333333 1.0000000 1.5 1
[9] {A,C} => {B} 0.3333333 0.5000000 1.5 1





share|improve this answer
























  • It works perfectly! Thank you very much!

    – Nicolò Manca
    Nov 22 '18 at 8:40











  • @NicolòManca please consider this, there is no obligation in it, but it points out to a wider community that the answer has a solution.

    – s_t
    Nov 22 '18 at 8:42













  • I cant vote cause i have less than 15 reputation point :(

    – Nicolò Manca
    Nov 22 '18 at 10:40











  • Sure thank you =)

    – Nicolò Manca
    Nov 22 '18 at 17:13











  • Just for reference. Look at the manual page ? transactions. Example 4 fits this case.

    – Michael Hahsler
    Nov 25 '18 at 16:25














0












0








0







You can try this, to convert your data.frame in a transaction dataset. I've added a fake date, but I think it's useless, due you are not using it in your elaboration:



data$Document.Date <- Sys.Date()
data
Sales.Document Material Document.Date
1 1 A 2018-11-21
2 1 B 2018-11-21
3 1 C 2018-11-21
4 2 A 2018-11-21
5 2 C 2018-11-21
6 3 A 2018-11-21


Now exactly your dataset: you can add data.frame() in the dplyr chain:



library(tidyverse)
library(plyr)
grouping_for_AA <- data %>%
group_by(Sales.Document, Material) %>%
dplyr::select(Sales.Document, Material, Document.Date) %>%
data.frame()


Now you can transform in a transactions data:



library(arules)
library(reshape2)
trans <- as(split(grouping_for_AA[,"Material"], grouping_for_AA[,"Sales.Document"]), "transactions")

inspect(trans)
items transactionID
[1] {A,B,C} 1
[2] {A,C} 2
[3] {A} 3


Lastly, you can apply the apriori() function:



rules <- apriori(trans, parameter = list(supp = 0.3, conf = 0.3, target="rules", minlen=2)) 
inspect(rules)
lhs rhs support confidence lift count
[1] {B} => {C} 0.3333333 1.0000000 1.5 1
[2] {C} => {B} 0.3333333 0.5000000 1.5 1
[3] {B} => {A} 0.3333333 1.0000000 1.0 1
[4] {A} => {B} 0.3333333 0.3333333 1.0 1
[5] {C} => {A} 0.6666667 1.0000000 1.0 2
[6] {A} => {C} 0.6666667 0.6666667 1.0 2
[7] {B,C} => {A} 0.3333333 1.0000000 1.0 1
[8] {A,B} => {C} 0.3333333 1.0000000 1.5 1
[9] {A,C} => {B} 0.3333333 0.5000000 1.5 1





share|improve this answer













You can try this, to convert your data.frame in a transaction dataset. I've added a fake date, but I think it's useless, due you are not using it in your elaboration:



data$Document.Date <- Sys.Date()
data
Sales.Document Material Document.Date
1 1 A 2018-11-21
2 1 B 2018-11-21
3 1 C 2018-11-21
4 2 A 2018-11-21
5 2 C 2018-11-21
6 3 A 2018-11-21


Now exactly your dataset: you can add data.frame() in the dplyr chain:



library(tidyverse)
library(plyr)
grouping_for_AA <- data %>%
group_by(Sales.Document, Material) %>%
dplyr::select(Sales.Document, Material, Document.Date) %>%
data.frame()


Now you can transform in a transactions data:



library(arules)
library(reshape2)
trans <- as(split(grouping_for_AA[,"Material"], grouping_for_AA[,"Sales.Document"]), "transactions")

inspect(trans)
items transactionID
[1] {A,B,C} 1
[2] {A,C} 2
[3] {A} 3


Lastly, you can apply the apriori() function:



rules <- apriori(trans, parameter = list(supp = 0.3, conf = 0.3, target="rules", minlen=2)) 
inspect(rules)
lhs rhs support confidence lift count
[1] {B} => {C} 0.3333333 1.0000000 1.5 1
[2] {C} => {B} 0.3333333 0.5000000 1.5 1
[3] {B} => {A} 0.3333333 1.0000000 1.0 1
[4] {A} => {B} 0.3333333 0.3333333 1.0 1
[5] {C} => {A} 0.6666667 1.0000000 1.0 2
[6] {A} => {C} 0.6666667 0.6666667 1.0 2
[7] {B,C} => {A} 0.3333333 1.0000000 1.0 1
[8] {A,B} => {C} 0.3333333 1.0000000 1.5 1
[9] {A,C} => {B} 0.3333333 0.5000000 1.5 1






share|improve this answer












share|improve this answer



share|improve this answer










answered Nov 21 '18 at 19:18









s_ts_t

3,25821031




3,25821031













  • It works perfectly! Thank you very much!

    – Nicolò Manca
    Nov 22 '18 at 8:40











  • @NicolòManca please consider this, there is no obligation in it, but it points out to a wider community that the answer has a solution.

    – s_t
    Nov 22 '18 at 8:42













  • I cant vote cause i have less than 15 reputation point :(

    – Nicolò Manca
    Nov 22 '18 at 10:40











  • Sure thank you =)

    – Nicolò Manca
    Nov 22 '18 at 17:13











  • Just for reference. Look at the manual page ? transactions. Example 4 fits this case.

    – Michael Hahsler
    Nov 25 '18 at 16:25



















  • It works perfectly! Thank you very much!

    – Nicolò Manca
    Nov 22 '18 at 8:40











  • @NicolòManca please consider this, there is no obligation in it, but it points out to a wider community that the answer has a solution.

    – s_t
    Nov 22 '18 at 8:42













  • I cant vote cause i have less than 15 reputation point :(

    – Nicolò Manca
    Nov 22 '18 at 10:40











  • Sure thank you =)

    – Nicolò Manca
    Nov 22 '18 at 17:13











  • Just for reference. Look at the manual page ? transactions. Example 4 fits this case.

    – Michael Hahsler
    Nov 25 '18 at 16:25

















It works perfectly! Thank you very much!

– Nicolò Manca
Nov 22 '18 at 8:40





It works perfectly! Thank you very much!

– Nicolò Manca
Nov 22 '18 at 8:40













@NicolòManca please consider this, there is no obligation in it, but it points out to a wider community that the answer has a solution.

– s_t
Nov 22 '18 at 8:42







@NicolòManca please consider this, there is no obligation in it, but it points out to a wider community that the answer has a solution.

– s_t
Nov 22 '18 at 8:42















I cant vote cause i have less than 15 reputation point :(

– Nicolò Manca
Nov 22 '18 at 10:40





I cant vote cause i have less than 15 reputation point :(

– Nicolò Manca
Nov 22 '18 at 10:40













Sure thank you =)

– Nicolò Manca
Nov 22 '18 at 17:13





Sure thank you =)

– Nicolò Manca
Nov 22 '18 at 17:13













Just for reference. Look at the manual page ? transactions. Example 4 fits this case.

– Michael Hahsler
Nov 25 '18 at 16:25





Just for reference. Look at the manual page ? transactions. Example 4 fits this case.

– Michael Hahsler
Nov 25 '18 at 16:25


















draft saved

draft discarded




















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid



  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.


To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53411428%2ftransform-a-dataframe-to-a-transaction-object-for-the-apriori-function-without-e%23new-answer', 'question_page');
}
);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

"Incorrect syntax near the keyword 'ON'. (on update cascade, on delete cascade,)

Alcedinidae

Origin of the phrase “under your belt”?