One Year Of Cubing

9 minute read

Se ha cumplido un año desde que compré mi primer 2x2x2 y mi primer 3x3x3 decente… ¿Qué ha pasado desde entonces?

(Tools used: R and Highcharts wrapped by Highcharter)

Data Wrangling

La fuente es un google doc que llevo al día con toda la información de la colección. Lo descargo como tsv y creo un data.table:

library(data.table)

cubos.dt <- fread(input = "data/Compras - Datos.tsv", header = T, select = seq(1, 19), dec = ',', nrows = 107)

Ya tengo en cubos.dt las primeras 19 columnas de las primeras 107 filas de mi fichero exportado; con cabecera, teniendo el tabulador como separador de columnas y la coma como separador de decimales. Un poco de formateo de los datos y estamos listos para empezar:

library(lubridate)

cubos.dt[, c(15:17) := lapply(.SD, dmy), .SDcols = c(15:17)]
columnasFactor <- c(2, 4, 8, 9, 14)
cubos.dt[, c(columnasFactor) := lapply(.SD, factor), .SDcols = c(columnasFactor)] 

Asi quedan formateadas como date las columas de la 15 a la 17 y como factor las columnas: 2, 4, 8, 9, 14.

Estructura definitiva de la tabla:

library(knitr)

str(cubos.dt)
## Classes 'data.table' and 'data.frame':	107 obs. of  19 variables:
##  $ N         : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ Brand     : Factor w/ 23 levels "Calvin","Cube4You",..: 18 18 18 18 18 18 18 18 18 18 ...
##  $ Model     : chr  "Thunderclap V2 (3x3)" "QiDi S (2x2)" "Warrior W (3x3)" "QiYuan S (4x4)" ...
##  $ Type      : Factor w/ 17 levels "Copter Mod","Cube",..: 2 2 2 2 2 14 8 10 11 8 ...
##  $ Layers    : chr  "3" "2" "3" "4" ...
##  $ Faces     : int  6 6 6 6 6 6 4 6 4 12 ...
##  $ Pieces    : int  26 8 26 56 98 14 14 26 26 62 ...
##  $ Geometry  : Factor w/ 20 levels "4 Corner Hexagonal Dipyramid",..: 2 2 2 2 2 2 16 2 6 8 ...
##  $ Colors    : Factor w/ 19 levels "Black","Black (Carbon Red)",..: 1 14 14 14 14 14 14 19 14 14 ...
##  $ Mod       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ Mag       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ Def       : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ Price     : num  9.99 5 6 7.9 8.99 3.46 3.46 1.67 2.05 3.85 ...
##  $ Store     : Factor w/ 7 levels "kubekings","lezul",..: 2 2 2 2 2 7 7 7 7 7 ...
##  $ Released  : Date, format: "2016-07-06" "2017-03-20" ...
##  $ Ordered   : Date, format: "2017-10-21" "2017-10-21" ...
##  $ Received  : Date, format: "2017-10-21" "2017-10-21" ...
##  $ Image     : chr  "1_Thunderclap.jpg" "2_QiDi.jpeg" "3_Warrior.jpg" "4_QiYuan.jpg" ...
##  $ Difficulty: num  1.5 1 1.5 2 2 1 1 2 2.49 2 ...
##  - attr(*, ".internal.selfref")=<externalptr>
kable(cubos.dt[1:2, 1:8], caption = 'First 8 columns of first 2 rows')
N Brand Model Type Layers Faces Pieces Geometry
1 QiYi Thunderclap V2 (3x3) Cube 3 6 26 Cube
2 QiYi QiDi S (2x2) Cube 2 6 8 Cube

Big Picture

La primera aproximación será ver la evolución de compras a lo largo del año:

totalEvolution.dt <- cubos.dt[, .(Ordered)]
totalEvolution.dt <- totalEvolution.dt[, .(cubes = .N), by = Ordered]
setorder(totalEvolution.dt, Ordered)

kable(head(totalEvolution.dt, 3), caption = 'First 3 rows')
Ordered cubes
2017-10-21 2
2017-10-27 3
2017-11-07 5
library(highcharter)

highchart() %>%
  hc_title(text = "Evolution") %>%
  hc_xAxis(type = 'datetime', title = list(text = "Date"), style = list(fontWeight = "bold")) %>%
  hc_yAxis(title = list(text = "Cubes")) %>%
  hc_add_series_times_values(dates = totalEvolution.dt$Ordered, values = cumsum(totalEvolution.dt$cubes),
                             name = 'Cubes', type = 'areaspline') %>%
  hc_tooltip(shared = T)  %>%
  hc_add_theme(hc_theme_google())

Open

Empecé con tan solo 2 cubos (un 2x2 y un 3x3) comprados en una tienda de barrio. Me gustaron tanto que volví a la semana siguiente a comprar 3 más (otro 3x3, un 4x4 y un 5x5). Lejos de quedarme satisfecho, en menos de dos semanas ya estaba haciendo mi primer pedido online a un almacén de China.

Hasta primeros de mayo compras regulares bastante espaciadas. Luego 3 meses de descanso hasta que en agosto se me despierta el afan coleccionista y realizo tantas compras que casi doblo el número de cubos que tenía hasta ese momento. Después de la locura veraniega parece que vuelve la normalidad hasta que se cumple el primer año desde la primera compra.

En total 107 cubos.

Monthly Detail

Tras la visión global es hora de una mirada más detallada por meses:

splitMonths.dt <- cubos.dt[, .(Ordered, Store)]
splitMonths.dt <- splitMonths.dt[, .(cubes = .N, month = floor_date(Ordered, unit = 'month')), by = .(Ordered, Store)]
splitMonths.dt <- splitMonths.dt[, .(cubes = sum(cubes), orders = .N), by = month]
emptyMonths.dt <- data.table(month = c('2018-01-01', '2018-06-01', '2018-07-01'))
emptyMonths.dt[, month := ymd(month)]
splitMonths.dt <- rbindlist(list(splitMonths.dt, emptyMonths.dt), use.names = T, fill = T)
setorder(splitMonths.dt, month)

highchart() %>%
  hc_title(text = "Data by Month") %>%
  hc_xAxis(categories = paste(month(splitMonths.dt$month, label = T), substring(year(splitMonths.dt$month), 3)),
           title = list(text = "Month")) %>%
  hc_yAxis_multiples(
    list(title = list(text = "Cubes", style = list(color = hc_theme_google()$colors[1], fontWeight = "bold"))),
    list(opposite = T, title = list(text = "Orders", style = list(color = hc_theme_google()$colors[2],
                                                                  fontWeight = "bold"))),
    list(visible = F),
    list(visible = F)
  ) %>%
  hc_add_series(data = splitMonths.dt$cubes, name = 'Cubes', type = 'column') %>% 
  hc_add_series(data = splitMonths.dt$orders, yAxis = 1, name = 'Orders', type = 'column') %>%
  hc_add_series(data = round(splitMonths.dt$cubes / splitMonths.dt$orders, digits = 2), yAxis = 2, type = 'spline',
                dashStyle='Dash', lineWidth = 1, name = 'Cubes / Order', connectNulls = T) %>% 
  hc_tooltip(shared = T) %>%
  hc_add_theme(hc_theme_google())

Open

Se ve claramente que enero, junio y julio son los meses de descanso y agosto el mes del desmadre. El resto de meses oscila entre uno o dos pedidos salvo el último con 3. En él se hace notar que en ocasiones es difícil encontrar determinados cubos. El hecho de que tenga la menor relación de cubos por pedido se debe a que tengo que recurrir a tiendas distintas para comprar cubos concretos que empiezan a escasear.

La comparación del número de cubos por mes se aprecia mejor en el siguiente gráfico:

library(viridisLite)

cubesMonth.dt <- splitMonths.dt[, .(month, cubes)]
cubesMonth.dt[, `:=`(month = paste0(month(cubesMonth.dt$month, label = T), " '",
                                    substring(year(cubesMonth.dt$month), 3)))]
setorder(cubesMonth.dt, -cubes)
highchart() %>%
  hc_title(text = 'Cubes-Month Comparison') %>% 
  hc_colors(plasma(13)) %>%
  hc_add_series(cubesMonth.dt, type = "pyramid", hcaes(x = month, y = cubes), name = 'Cubes')

Open

Se podría decir que los meses vacacionales son los más abultados con diferencia.

Price Distribution

priceHistogram.dt <- cubos.dt[, .(Price)]

hchart(priceHistogram.dt$Price, name = 'Price') %>% hc_yAxis(title = list(text = "Number of Cubes")) %>%
  hc_add_theme(hc_theme_google())

Open

Afortunadamente la mayoría de cubos de momento cae en la categoría de (0-5]€. Según aumenta el precio disminuye el número de miembros del grupo salvo el caso (15-20]€ que curiosamente es más numeroso que el intervalo anterior.

Stores

Un poco de análisis por tienda…

Number Of Cubes

shopsInfo.dt <- cubos.dt[, .(Store, Ordered)]
shopsInfo.dt <- shopsInfo.dt[, .(cubes = .N), by = .(Store, Ordered)]
shopsInfo.dt <- shopsInfo.dt[, .(cubes = sum(cubes), orders = .N), by = .(Store)]
setorder(shopsInfo.dt, -cubes, Store)
shopsInfo.dt[, `:=`(name = Store, value = cubes, Store = NULL, cubes = NULL, orders = NULL, color = plasma(7))]

highchart() %>%
  hc_title(text = 'Number Of Cubes By Store') %>%
  hc_xAxis(categories = shopsInfo.dt$Store) %>% 
  hc_add_series(shopsInfo.dt, name = "Cubes", type = 'treemap')

Open

Este gráfico refleja mi experiencia con las tiendas:

  1. zcube: Precios muy competitivos, envío con seguimiento y muy rápido a pesar de venir de China. Tiene la página web mas atractiva de todas.
  2. kubekings y lezul: Mis tiendas españolas de referencia (online y física respectivamente) por su eficiencia y exquisita amabilidad que en muchas ocasiones hace ignorar posibles mejores precios de la competencia.
  3. lightake y mefferts: Tiendas con cubos exclusivos o que no encuentro en las anteriores. Buenos precios en la primera, no tanto en la segunda.
  4. puzzlesdeingenio y losmundosderubik: Tiendas españolas que me llamaban la atención y que he probado con distinta suerte. La experiencia con la primera fue satisfactoria y repetiré. En la segunda ignoraron todos mis intentos de contactar cuando el envío se retrasaba el doble del tiempo estimado. No puedo recomendarla.
  5. ukcubestore: Experiencia incluso peor que con losmundosderubik. Venden productos sin existencias y esperan a que reclames por el pedido que no llega. En ese momento te proponen alternativas en stock o devolverte el dinero. Si eliges reembolso te ignoran completamente hasta que reclamas vía Paypal. A día de hoy siguen mostrando en stock un producto que hace meses me dijeron que estaba agotado.

Prices

priceStoreDist.dt <- cubos.dt[, .(Price, Store)]
#Ignoring stores with only one cube
priceStoreDist.dt <- priceStoreDist.dt[, N := .N, by = Store][N > 1]

hcboxplot(x = priceStoreDist.dt$Price, var = priceStoreDist.dt$Store, name = 'Price', tooltip = list(valueSuffix = ' €'), outliers = T) %>%
  hc_title(text = 'Prices By Store') %>%
  hc_yAxis(min = 0, title = list(text = "Price")) %>%
  hc_chart(type = 'column') %>%
  hc_add_theme(hc_theme_google())

Open

La tienda más cara con diferencia es mefferts. El cubo más barato que compré ahí tiene casi el mismo precio que el más caro comprado en zcube que es donde compré el más barato y su mediana solo se ve mejorada por lightake. La mediana de kubekings y lezul es muy parecida resultando algo superior en el caso de la segunda aunque hay que decir a su favor que es la única en la que no habría que añadir gastos de envío.

En cualquier caso, este gráfico no representa los precios de las tiendas en general sino del tipo de cubos que compré en ellas.

Brands

Ahora una visión centrada en las marcas…

Number Of Cubes

brandsInfo.dt <- cubos.dt[, .(Brand, Price)]
brandsQuantityDist.dt <- brandsInfo.dt[, .(value = .N), by = .(Brand)]
#First order, then asign colors and name
setorder(brandsQuantityDist.dt, -value)
brandsQuantityDist.dt[, `:=`(name = Brand, color = plasma(nrow(brandsQuantityDist.dt)))]

highchart() %>%
  hc_title(text = 'Number Of Cubes By Brand') %>%
  hc_xAxis(categories = brandsQuantityDist.dt$Brand) %>% 
  hc_add_series(brandsQuantityDist.dt, name = "Cubes", type = 'treemap')

Open

Este gráfico muestra una comparativa de la cantidad de cubos por marca en la colección. Coincide con el top 3 de mis favoritas pero hay algunas como DaYan, WitEden y sobre todo mf8 que están muy por debajo de lo que deberían porque he comprado menos a causa de sus precios y de sus tipos de cubo tan especializados que son los últimos en unirse a la colección. El tiempo les irá acercando a los primeros puestos…

Prices

# Ignoring Brands with only one cube
brandsInfo.dt <- brandsInfo.dt[, N := .N, by = Brand][N > 1]

hcboxplot(x = brandsInfo.dt$Price, var = brandsInfo.dt$Brand, name = 'Price',
          tooltip = list(valueSuffix = ' €'), outliers = T) %>%
  hc_title(text = 'Prices By Brand') %>%
  hc_yAxis(min = 0, title = list(text = "Price")) %>%
  hc_chart(type = 'column') %>%
  hc_add_theme(hc_theme_google())

Open

Mucha información… Destacaré 3 titulares:

  • Calvin es sin lugar a dudas la marca más cara hasta el momento.
  • MoYu es la que abarca un abanico más amplio de precios.
  • QiYi es la que tiene más distancia entre el cubo más caro y el resto. Es lo que tiene alternar entre su línea barata y uno de sus cubos magnéticos de competición.

Leave a comment